[js] 동기화 vs 비동기화 3 / 3

이벤트와 콜백은 실제 시간상의 대기가 필요하기 때문에 지속적으로 프레임에서 감시하는 방식이었습니다.
하지만 비동기화의 또 다른 목적은 블로킹(blocking)방지입니다. 마지막으로 블로킹 방지를 위한 비동기화에 대해 살펴보는 걸로 시리즈를 마무리합니다.

내장 xml 파서에 대해top

json 은 구석구석 사용되고 있지만 수 많은 공용 데이터 시스템에서는 여전히 xml 과 통신해야 합니다.하지만 파싱하기도 귀찮고 파서들의 메서드들도 어쩜 그리 쓰기 불편한지 스트레스가 이만저만이 아닙니다.

현재 거의 모든 브라우저는 xml 을 파싱할 수 있는 내장 객체를 갖고 있는데 크게 세 종류입니다.

  1. 우선 브라우저의 dom 파서를 그대로 xml 파서로 사용할 수 있게 내장 객체로 제공하는 경우 입니다. 파폭, 크롬을 비롯해 다수가 여기에 속합니다.
  2. IE 구버전은 activeX 객체로 갖고 있습니다. 버전도 다양합니다.
  3. XHR 도 데이터 반환 시 xml 로 반환하는 능력이 있습니다.

하지만 진짜 문제는 저 파서들의 메서드가 너무 사용하기 불편하다는 겁니다. 해서 기저에 저런 파서 중 하나를 선택적으로 지정해주고 실제 파싱한 결과를 별도의 해시맵으로 반환하게 하려합니다.

브랜든 할베가 E4X 를 밀고 있습니다만, 사실 그것도 불편합니다. 목표로 하는 결과집합은 아래와 같은 기능을 갖추게 될 것 입니다.

  • 태그 안의 내용을 value 속성으로 참조할 수 있다.
  • 같은 계층에서 같은 이름의 태그가 여러 개 존재하면 자동으로 리스트화 시킨다.
  • 속성은 $를 통해 참조할 수 있다.

아래와 같은 기능은 구현하지 않습니다.

  • xsl과 xslt…내장객체 기능이 미약하시어 전부 수동으로 구축해야 하므로 포기
  • 네임스페이스 미지원…이것도 너무 내장 객체들의 기능이 허접하여…
    상당히 중요한 기능인데 분량 상 다 다루면 아무도 안볼 길이의 글이 되므로 생략합니다.

여담인데 순수히 자바스크립트만으로 완전히 xml 파서를 구축한건 이미 파워빌더시절에 웹으로 출력하면 파워빌더가 해줬을 정도로 오래 전입니다(그 때 참 감탄했던 기억이..)

 

파서APItop

최대한 간단한 인터페이스로 구축합니다. xml 데이터를 넘겨주고 오브젝트를 받는 거죠. 데이터의 로딩이나 기타 처리는 이 파서의 책임이 아닙니다.
따라서 가장 간단한 인터페이스는 아래와 같을 것입니다.

<script type="text/data" id="data">
<![CDATA[
<books>
	<book category="컴퓨터">
		<title>javascript1</title>
		<time>2013.10.20</time>
		<img>1.png</img>
	</book>
	<book category="요리">
		<title>김밥만들기</title>
		<time>2013.10.25</time>
		<img>2.png</img>
	</book>
</books>
]]>
</script>
var result = xml( document.getElementById("data").text );

또한 일단 파싱이 끝난 객체는 위의 스펙대로의 설명이라면 아래와 같이 사용할 수 있게 됩니다.

//value를 통한 내용참조
result.books.book[0].title.value == "javascript1"
result.books.book[1].img.value == "2.png"

//속성참조
result.books.book[1].$category == "요리"

//루프를 위한 length 지원
var i = result.books.book.length;
while( i-- ) console.log( "published:" + result.books.book[i].time );

하지만 xml은 때때로 믿을 수 없을 정도로 길어집니다. 그렇게 되면 파싱자체도 스크립트 타임아웃에 걸려 불가능하고 설령 가능해도 블로킹되는 시간도 너무 길어집니다.
그래서 긴 xml을 대비하여 비동기를 지원하게 만들어야 합니다. 두번째 인자가 온다면 비동기로 작동하게 만듭니다.

//동기모드
var result = xml( data );
console.log( result.books.book[1].img.value );

//비동기모드
xml( data, function( result ){
	console.log( result.books.book[1].img.value );
} );

인터페이스의 구조는 확립했으니 이제 차근차근 구현해볼 차례입니다.

 

기저파서얻기top

우선 브라우저에 내장된 xml 파서를 감싸 크로스브라우저층을 구축해야 합니다. 또한 기저파서가 무엇이냐에 따라 같은 역할을 하는 속성에 이름이 차이가 생깁니다. 파서에 따른 타입을 구분해둘 필요가 있습니다. 간단히 코드로 봅니다.

if( window['DOMParser'] ){
	type = 1;
	parser = new DOMParser;
}else{
	type = 0;
	parser = (function(){
		var t0, i, j;
		t0 = 'MSXML2.DOMDocument';
		t0 = ['Microsoft.XMLDOM','MSXML.DOMDocument',
		  t0,t0+'.3.0',t0+'.4.0',t0+'.5.0',t0+'.6.0'];
		i = t0.length;
		while( i-- ){
			try{ new ActiveXObject( j = t0[i] ); }catch( $e ){ continue; }
			break;
		}
		return new ActiveXObject( j );
	})();
}

IE 의 경우 최신 파서와 오래된 파서는 속도 차이가 허벌납니다. 꼭 귀찮아말고 최신순으로 찾게 해야 성능이 나옵니다.
일단 기저파서와 타입을 확정지었으니 이 수준에서 xml 을 파싱한 결과를 오브젝트로 바꾸기만 하면 됩니다. xml 를 오브젝트로 바꾸는 함수를 toObj 라 하면 간단히 xml함수를 정리할 수 있습니다.

if( type == 1 ){
	xml = function( $data, $loaded ){
		$data = parser.parseFromString( $data, "text/xml" );
		return toObj( $data, $loaded );
	};
}else{
	xml = function( $data, $loaded ){
		parser.loadXML( $data );
		return toObj( parser, $loaded );
	};
}

DOMParser 의 경우는 파싱한 결과를 반환하지만 activeX파서는 반환값이 없고 그 파서 자체를 이용해야 합니다.

 

재귀함수와 도입점함수top

함수형 프로그래밍에 익숙한 분들은 자동으로 처리되는 tail recursive에 익숙합니다. 하지만 제어형 언어에서는 반드시 반복문을 통해 표현하지 않으면 선형 재귀 프로세스가 되어 스택오버플로가 일어납니다.

..(설명할까말까 1분간 고민 중)..

..
..요즘은 글쓰다가 너무 삼천포로 빠지는 느낌도 있는데..
..
.
하기로..=.=;
.

재귀호출과 반복문

이 섹션은 건너뛰셔도 됩니다. 그냥 제어형 언어에서 재귀를 배제하고 왜 반복문으로 알고리즘을 짜야하는지에 대한 얘기입니다.
다음의 코드는 문자열을 분석하여 해당되는 형으로 바꾸는 함수입니다.

function getValue( $data ){
	if( $data.charAt(0) == '[' ){ //배열이다!
		return getArray( $data );
	}else if( $data.charAt(0) == '{' ){ //오브젝트다!
		return getObject( $data );
	}else if( /^[0-9.-]+$/.test( $data ) ){ //숫자!
		return parseFloat( $data );
	}else if( $data == 'true' || $data == 'false' ){//불린!
		return Boolean( $data );
	}else{ //아님 대략 문자열로 퉁침!
		return $data;
	}
}

이 상황에서는 아직 재귀함수로 보이지 않지만 재귀란 반드시 자기 자신을 호출해야만 하는 것은 아닙니다. recursive 의 개념은 기본적으로 순환이기 때문에 다시 순환하면 얼마든지 재귀의 가능성이 있습니다. getArray 함수를 구축해보죠.

function getArray( $data ){
	//1. 앞 뒤의 있는 [ 와 ] 를 벗겨내자.
	$data = $data.substring( 1, $data.length - 1 );

	var result = []; //결과 저장소
	var item = ''; //개별요소
	var depth = 0; //깊이관리

	for( var i = 0, j = $data.length ; i < j ; i++ ){

		//컴마를 발견할 때, depth가 0일 때만 추가하고 item을 초기화
		if( !depth && $data[i] == ',' ){
			result.push( item );
			item = '';
		}else{
			//item에 문자를 추가하고
			item += $data[i];

			//배열이나 오브젝트라면 depth증가
			if( $data[i]=='[' || $data[i]=='{' ) depth++;

			//빠져나올땐 depth감소
			else if( $data[i]==']' || $data[i]=='}' ) depth--;
		}
	}

	//완성된 result의 요소를 다시 getValue 로 타입을 확정!
	i = result.length;
	while(i--) result[i] = getValue( result[i] );

	return result;
}

getArray함수는 간단한 토크나이저(tokenizer)입니다.

  1. 우선 앞 뒤의 [] 를 제거한 뒤
  2. 전체 문자열을 쭉 돌면서 컴마가 나올 때마다 배열에 넣어주고 있습니다.
  3. 단지 내부에 다시 배열이나 오브젝트가 선언된 경우는 그 객체가 다시 닫힐 때까지 하나의 요소로 간주해야하기 때문에 depth를 이용하고 있습니다.
  4. 하지만 이렇게 분리된 배열의 요소는 그저 텍스트일 뿐이므로 배열을 다시 루프돌며 각 요소를 getValue로 형변환해야합니다.

바로 이 마지막 시점에 getValue를 호출하게 됩니다.
따라서 getValue – getArray – getValue – getObject – getArray – getValue….이런 식으로 전체가 완전히 파싱될 때까지 순환호출하게 됩니다. 만약 다음과 같은 데이터가 존재하다고 가정해보겠습니다.

var data = '[str,37,[50,aa,[1,2]],true]';

위의 getValue 구조를 생각해보면 순서대로 다음과 같이 호출됩니다.

getValue( '[str,37,[50,aa],true]' ){
	getArray( '[str,37,[50,aa],true]' ){
		getValue( str );
		getValue( 37 );
		getValue( [50,aa] ){
			getArray( [50,aa] ){
				getValue( 50 );
				getValue( aa );
			}
		}
		getValue( true );
	}
}

여기서 표현한 중괄호가 스택입니다. 즉 최대 4단계까지 스택이 쌓이게 됩니다. 함수를 호출하면 결과값을 반환할 때까지 스택이 남게 됩니다. 반대로 getArray의 마지막 부분은 while 문을 이용해 요소를 처리하기 때문에 스택이 쌓이지 않고 매번 클리어됩니다.

  1. getValue 가 배열이나 오브젝트를 만날 때 처리함수를 내부에서 호출하면 호출스택이 쌓이지만
  2. getArray 의 마지막 부분처럼 while, for 등의 내장된 반복문을 사용하면 루프돌 때마다 스택이 클리어된다

는 것입니다.
함수 내부에서 다른 함수를 호출하는 행위 자체를 없애는건 불가능하지만 되도록이면 중간중간 반복되는 부분을 그냥 함수를 호출하지 않고 반복문으로 막아줘야 스택이 꽉차버리는걸 막을 수 있습니다.

제어형 언어는 반드시 반복문으로 로직을 바꾸지 않으면 스택오버플로우가 생깁니다. 예를 들어 위의 getValue는 중첩된 배열이 나올때마다 스택이 증가하므로 간단히 다음과 같이 방법으로 다운시킬 수 있습니다.

var result = getValue( '[[[[[[[[[[[1]]]]]]]]]]' );

위에서 50개 정도의 배열을 충첩한 문자열을 보내면 getValue – getArray – getValue.. 를 연쇄하며 간단히 스택오버플로우가 됩니다.
하지만 여기서부터가 진짜 문제인데…

짜증나게시리 스택오버플로우는 디버깅이 불가능하다는 것입니다.
정확하게 말하면 스택오버플로우라는 현상 자체는 감지할 수 있어도, 수정할 방법이 없습니다. getValue, getArray에 틀린 곳은 없기 때문입니다.
게다가 발생을 하는 것도 들어온 data에 달려있는 것이기 때문에 테스트시에는 잘도 통과합니다.
하지만 궁극적으로 recursive 가 되는 모든 함수내 호출은 잠정적으로 오류라고 확정지어도 됩니다.

따라서 제어형 언어를 사용하는 이상 recusive 를 배제해야 합니다. 그럼 어떻게 해야할까요?

이상적으로는 모든 로직은 반복문을 통해 재작성해야 합니다. 실제 수학적으로 증명하면 그것은 가능하긴 합니다. 하지만 인간이 그 방식으로 생각하는게 매우 어렵습니다.
재귀함수나 recusive 되는 순환 호출 구조가 탄생하는 이유는 근본적으로 그렇게 인간이 이해하는 것이 더 두뇌에 편하기 때문입니다.

따라서 차안은 되도록이면 중간중간 반복문으로 끊어주는 것입니다. 기존에 함수를 호출하던 것을 반복문으로 옮기면 개별 함수를 호출시 부여받던 독립적인 스택메모리 공간이 없어지므로 그 함수 내부에 있는 지역변수 전부를 가져와야 합니다.
예를 들어 getValue가 더 이상 getArray에 의존하지 않게 해보죠.

function getValue( $data ){
	if( $data.charAt(0) == '[' ){ //배열이다!
		$data = $data.substring( 1, $data.length - 1 );
		var result = [], item = '', depth = 0;
		for( var i = 0, j = $data.length ; i < j ; i++ )
			if( !depth && $data[i] == ',' ){
				result.push( item );
				item = '';
			}else{
				item += $data[i];
				if( $data[i]=='[' || $data[i]=='{' ) depth++;
				else if( $data[i]==']' || $data[i]=='}' ) depth--;
			}
		i = result.length;
		while(i--) result[i] = getValue( result[i] );
		return result;
	}else if( $data.charAt(0) == '{' ){
		return getObject( $data );
	}else if( /[0-9.-]/.test( $data ) ){
		return parseFloat( $data );
	}else if( $data == 'true' || $data == 'false' ){
		return Boolean( $data );
	}else{
		return $data;
	}
}

엥 그냥 getArray 를 가져온거 아니냐구요? 맞습니다. 근데 그 덕분에 getArray에 있던 수 많은 변수도 이사와버렸습니다. getObject 까지 포함하면 더욱 많아질 것입니다.
하지만 덕분에 더 이상 getArray 를 호출하지 않습니다. 스택단계가 하나 더 줄어든거죠. 위에는 50번만에 스택오버플로우로 뻗어버리던 getValue 는 이제 100번까지 중첩해도 잘 버텨낼 것입니다.

제어형 언어의 이 짜증나는 점은 상태를 기반으로 하기 때문입니다. 상태라는 관점에서 함수를 보면 독립된 상태공간을 만드는 행위이므로 호출할 때마다 메모리가 잠식되고 전부 해결될 때까지는 해지되지 않는 모순이 생깁니다.
앞 서 말했듯 스택오버플로우는 일단 발생하면 알고리즘 수준을 전부 교체해야하기 때문에 프로젝트 말에 생기면 손델 수 없게 됩니다.
처음부터 recusive 로직을 인식하고 제거해야 합니다.

도입점함수

수고하셨습니다. 이제 다시 xml파서로 돌아와서 도입점함수를 제작하겠습니다. 도입점함수는 스택오버플로우를 막기 위해 각 노드처리 함수를 직접 재귀하지 않고 반복문으로 바꾸기 위한 장치 입니다. 바로 toObj 함수입니다.
꽤나 오래 전(^^;) 이므로 다시 기저파서를 이용한 xml함수를 포함하여 코드를 보겠습니다.

if( type == 1 ){
	xml = function( $data, $loaded ){
		$data = parser.parseFromString( $data, "text/xml" );
		return toObj( $data, $loaded );
	};
}else{
	xml = function( $data, $loaded ){
		parser.loadXML( $data );
		return toObj( parser, $loaded );
	};
}

function toObj( $data, $loaded ){
	var temp = $data.childNodes;
	var r = {};
	for( var i = 0, j = temp.length ; i < j ; i++ ){
		var n = type ? temp[i] : temp.nextNode();
		r[node.nodeName] = node( n );
	}
	return r;
}

toObj 함수는 파싱된 xml 객체로부터 childNodes 를 얻어 이를 루프돌며 node 함수를 호출하여 결과를 result에 차곡차곡 넣게 됩니다.

하지만 생각해보면 자식 노드도 childNodes가 있을 것이므로, node함수에도 childNodes를 루프도는 로직이 들어있을 것입니다.
근데 왜 구지 toObj가 존재하는가 하면 우선 도입점함수로서 r 을 만드는 역할과 전체 작업을 우선적으로 반복문으로 쪼개 스택오버플로우를 방지하기 위해서 입니다.

node함수

node함수는 가장 핵심적인 기능을 담당합니다. xml객체의 메서드를 통해 result에 차곡차곡 데이터를 넣어줍니다.

function node( $node ){
	var node, r, n, child, i, j;
	node = $node.childNodes;
	r = {};
	//자식노드를 순회하며 r을 채운다
	for( i = 0, j = node.length ; i < j ; i++ ){
		//DOMParser와 AX는 자식노드를 얻는 방법이 다름.
		child = type ? node[i] : node.nextNode();

		//텍스트노드라면 value에 넣어준다.
		if( child.nodeType == 3 ){
			r.value = type ? child.textContent : child.text;
			continue;
		}

		n = child.nodeName; //노드이름
		child = node( child ); //해당노드를 다시 재귀로 정리

		if( r[n] ){ //이미 존재하는 노드라면

			//2번째 인경우 리스트로 전환!
			if( r[n].length === undefined ){
				//기존건 0번에, 이번건 1번에 넣어 리스트화
				r[n] = {length:2, 0:r[n], 1:child};

			//3번째부터는 그냥 추가하면 됨
			}else{

				r[n][r[n].length++] = t0;
			}

		}else{ //최초 등장하는 노드는 리스트가 아님
			r[n] = t0;
		}
	}
	if( t0 = $node.attributes ){ //속성이 있다면 $로 정리
		for( i = 0, j = t0.length ; i < j ; i++ ) r['$'+t0[i].name] = t0[i].value;
	}
	return r;
}

자식노드는 계속 자식 노드를 갖을 수 있기에 노드이름 등의 정보를 빼낸 뒤엔 다시 node함수를 재호출하여 오브젝트를 얻게 됩니다. 각 단계별로 for를 사용하므로 개별 계층에는 스택을 클리어할 수 있습니다.
이 역시 계층이 깊은 xml을 전부 커버할 수 있는 것은 아닙니다만, 이번 예제에서는 이 정도만 사용할 계획입니다 ^^;

이제 처음 제시했던 호스트코드대로 잘 작동하는 전체 xml함수가 완성되었습니다. 코드를 모아서 한꺼번에 봅니다.

var xml = (function(){
	var type, parser;

	function node( $node ){
		var node, r, n, child, i, j;
		r = {}, node = $node.childNodes;
		for( i = 0, j = node.length ; i < j ; i++ ){
			child = type ? node[i] : node.nextNode();
			if( child.nodeType == 3 ){
				r.value = type ? child.textContent : child.text;
				continue;
			}
			n = child.nodeName;
			child = node( child );
			if( r[n] )
				if( r[n].length === undefined ){
					r[n] = {length:2, 0:r[n], 1:child};
				}else{
					r[n][r[n].length++] = t0;
				}
			}else{
				r[n] = t0;
			}
		}
		if( t0 = $node.attributes )
			for( i = 0, j = t0.length ; i < j ; i++ ) r['$'+t0[i].name] = t0[i].value;
		return r;
	}

	function toObj( $data, $loaded ){
		var temp, r, i, j, n;
		temp = $data.childNodes, r = {};
		for( i = 0, j = temp.length; i < j ; i++ ){
			n = type ? temp[i] : temp.nextNode();
			r[node.nodeName] = node( n );
		}
		return r;
	}

	if( window['DOMParser'] ){
		type = 1;
		parser = new DOMParser;
		return function( $data, $loaded ){
			$data = parser.parseFromString( $data, "text/xml" );
			return toObj( $data, $loaded );
		};
	}else{
		type = 0;
		parser = (function(){
			var t0, i, j;
			t0 = 'MSXML2.DOMDocument',
			t0 = ['Microsoft.XMLDOM','MSXML.DOMDocument',
				t0,t0+'.3.0',t0+'.4.0',t0+'.5.0',t0+'.6.0'];
			i = t0.length;
			while( i-- ){
				try{ new ActiveXObject( j = t0[i] ); }catch( $e ){ continue; }
				break;
			}
			return new ActiveXObject( j );
		})();
		return function( $data, $loaded ){
			parser.loadXML( $data );
			return toObj( parser, $loaded );
		};
	}
})();

var result = xml( document.getElementById("data").text );

result.books.book[0].title.value == "javascript1"
result.books.book[1].img.value == "2.png"
result.books.book[1].$category == "요리"

 

비동기화 처리top

드디어 비동기화 처리를 하러 왔습니다…휴 길군요. 괜히 xml파서를 샘플로 선택한건가 싶습니다(하지만 for루프를 괜히 길게 만들어도 실감할 수 없기 때문에 실무와 좀 관련된 소스로 설명드리는게 좋다는 판단이었습니다)

앞 서 작성한 xml 함수를 보면 loaded 함수를 인자로 넘겨도 toObj에서 철저하게 무시당하고 있습니다. 일단 비동기로 작동시키는 이유는 다음과 같은 xml 때문입니다.

<nodes>
	<node a="3">a</node>
	<node a="3">a</node>
	<node a="3">a</node>
	...
</nodes>

노드가 수백개 있다면 toObj 함수의 for는 스크립트 타임아웃을 발생시켜 전체 페이지가 다운됩니다. 하지만 xml 이 길게 들어오는 것은 프로그램이 막을 수 있는 성격의 것이 아닙니다. 그저 데이터가 긴거죠. 예를들어 이번분기의 가입자 명단이라던가, 트래픽로그같은걸 받으면 그저 깁니다.
하지만 위에 작성한 로직을 그대로 적용하면 스크립트 타임아웃을 발생시켜 죽어버리므로 비동기화해야합니다.

개발자가 직접 비동기화를 처리할 수 있는 명령은 setTimeout 등입니다. 해서 2번째 인자로 콜백리스너가 들어온 경우에 한해 비동기로 처리하도록 toObj 를 개선해 봅니다.

function toObj( $data, $loaded ){
	var temp, r, n, i, j;
	temp = $data.childNodes, r = {};
	i = 0, j = temp.length;
	//리스너가 없으면 기존대로
	if( !$loaded ){
		for( ; i < j ; i++ ){
			n = type ? temp[i] : temp.nextNode();
			r[n.nodeName] = node( n );
		}
		return r;
	}

	//한번에 for를 돌리지말고 나눠서 돌리자!
	function loop(){ //프레임에서 반복할 함수
		var k, n;
		for( k = 0 ; i < j && k < 200 ; i++, k++ ){
			n = type ? temp[i] : temp.nextNode();
			r[n.nodeName] = node( n );
		}

		if( i < j ){ //아직 남았으면 다음프레임에서
			setTimeout( loop, 1 );
		}else{ //다 돌았으면 리스너에게 넘겨주자
			$loaded( r );
		}
	}

	//타이머시작!
	loop();
}

스코프 때문에 약간 어려울 수도 있습니다.

  1. i, j 는 loop 함수 입장에서 부모스코프에 존재하고 있습니다.
  2. k는 loop함수 내에의 지역변수로 loop 가 호출될 때마다 0~200으로 작동합니다.
  3. 루프조건에 i < j 와 동시에 k < 200 의 조건도 동시에 들어있죠.
  4. 예를 들어 천개의 노드가 있다면 j == 1000 입니다. 하지만 k는 200에서 끝이므로 loop 함수는 한번에 200개만 처리하고 다음 프레임으로 넘기게 됩니다.

이를 이용하면 한프레임에서 200번만 처리하고 나머지는 다음 프레임으로 지연시키므로 타임아웃이 걸리지 않는 적정 범위내에서 for를 사용할 수 있게 됩니다.
200이라는 숫자는 하드코딩된 숫자라 상황에 따라 다르게 적용해야하는 불편함이 있습니다. 이를 자동화하려면 최대한 보수적인 수치를 잡은뒤 프레임간 시간 경과를 비교하여 증감을 조정하는 기법을 사용합니다. 프레임당 증감시간을 조정하는 기법을 간단히 코드로 살펴보겠습니다.

var count, i, j, time;

//최소치만 정해둔다.
count = 10;

function loop(){
	var k, curr;
	for( k = 0 ; i < j && k < count ; i++, k++ ){
		//something
	}

	if( i < j ){
		//1. 현재 시간
		curr = +new Date;

		//2. 경과된 시간 - 0이면 1로
		k = curr - time || 1;

		//3. 루프몸체가 한번 처리되는데 걸리는 시간
		k /= count;

		//4. 0.1초 동안 처리할 수 있는 루프의 양
		count = parseInt( 100 / k );

		//5. 시간 리셋
		time = curr;

		//6. 0.1초 후 실행
		setTimeout( loop, 100 );
	}else{
		loaded();
	}
}
time = +new Date;
loop();

위의 코드는 0.1초 단위로 타이머를 도는데 매번 count계산을 새롭게 합니다. 이전 프레임에서 걸린 시간을 분석하여 다음 프레임에서 몇번을 돌릴지 계산하는거죠.

단지 자바스크립트는 나노단위까지 시간을 지원하지 않기 때문에 가벼운 로직인 경우 이전프레임과 현재 프레임의 시간차가 0이 될 수 있습니다. 그걸 방지하기 위해 0일 때는 1로 못박아 주는 것입니다.

 

결론top

총 3회에 걸쳐 비동기화와 동기화의 개념과 문제점, 해결점을 살펴봤습니다.

  1. 로직을 비동기화로 사용하는 이유는 실제 시간을 지연하고 싶어서, 스크립트 타임아웃을 피하기 위해서 등으로 요약됩니다.
  2. 개발자가 직접 비동기를 처리하는 방법은 setTimeout 등을 이용해 다른 프레임에 명령을 적재하는 것입니다.
  3. 다수의 비동기가 발생하면 통제가 어렵기 때문에 이런 경우 다시 일정 시점을 기준으로 동기화에 수렴하는 층을 구축하는 편이 좋습니다.

p.s
본 예제에 나온 xml 파서는 IE의 ax로 동작할 경우 단일한 오브젝트를 데이터객체로 재활용하게 되어있습니다. 비동기모드로 여러개를 동시 로딩하는 경우 문제가 생깁니다. 실무에서 동시에 여러 개의 xml을 로딩하는 경우도 있기 때문에 이 경우는 xml 함수에서 parser 를 재활용하는 것이 아닌 매번 새로운 parser 를 생성하도록 수정해주면 됩니다.