Chapter 1: Asynchrony: Now & Later

 자바스크립트와 같은 언어의 프로그래밍에서 가장 중요하지만 종종 오해를 받는 부분 중 하나는 일정 기간에 걸쳐 퍼져있는 프로그램 동작을 어떻게 표현하고 조작하는가 입니다.

 이는 for 루프의 시작부터 for 루프의 끝까지 발생하는 작업에 대한 것이 아닙니다. 이것은 프로그램의 일부가 지금(now) 실행되고 프로그램의 다른 부분이 나중에(later) 실행될 때 발생하는 현상에 대한 것입니다. 지금과 나중 사이에 프로그램이 능동적으로 실행되지 않는 간격이 있습니다.

 실제로 지금까지 작성된 모든 프로그램들이 사용자의 입력을 대기하기든, 데이터베이스나 파일 시스템에 데이터를 요청하든, 네트워크에 데이터를 요청하고 응답을 기다리기 또는 일정 시간마다 반복 작업을 수행하는 것이든 이러한 격차가 발생하는 곳에서 어떠한 방식을 사용해서라도 이 격차를 잘 처리해야 했습니다.

 사실 프로그램의 현재(now) 부분과 나중(later) 부분 간의 관계는 비동기 프로그래밍의 핵심입니다.

 비동기 프로그래밍은 JS가 시작된 이래로 존재해왔습니다. 그러나 대부분의 JS 개발자들은 어떻게 그리고 왜 그것이 그들의 프로그램에서 불쑥 나타나는지 신중하게 고려하거나 그것을 다루는 다양한 방법을 탐구한 적이 없습니다. 충분히 좋은 접근법은 항상 변변찮게 callback 함수를 사용하는 것이었습니다. 오늘날까지 많은 사람들은 콜백이 충분하다고 주장할 것입니다.

  그러나 JS가 범위와 복잡성 모두에서 지속적으로 성장함에 따라 브라우저와 서버, 그리고 그 사이의 가능한 모든 장치에서 실행되는 일류 프로그래밍 언어로써 점점 더 증가되는 요구를 충족시키기 위해, 우리가 비동기를 다루는 데 따르는 고통은 점점 더 장애가 되고 있습니다. 그리고 그들은 더 유용하고 합리적인 접근법을 요구하고 있습니다.

 이 모든 것이 지금은 다소 추상적으로 보일 수 있지만, 저는 우리가 이 책을 통해 이 문제를 더 완벽하고 구체적으로 다룰 것이라고 확신합니다. 우리는 다음 몇 장에 걸쳐 비동기 자바스크립트 프로그래밍을 위한 다양하고 새로운 기술들을 탐구할 것입니다.

 하지만 우리가 거기에 도달하기 전에, 우리는 비동기가 무엇인지 그리고 그것이 JS에서 어떻게 작동하는지 훨씬 더 깊이 이해해야 할 것입니다.

A Program in Chunks

 당신은 당신의 JS 프로그램을 하나의 .js 파일에 쓸 수 있지만, 당신의 프로그램은 거의 확실히 몇 개의 chunk로 구성되어 있습니다. 그 중 하나만 지금(now) 실행되고 나머지는 나중에(later) 실행될 것입니다. chunk의 일반적인 단위는 함수입니다.

 JS를 처음 접하는 대부분의 개발자들이 가지고 있는 의문점은 later가 정확히 now 직후에 일어나지 않는다는 것입니다. 즉, 지금 완료할 수 없는 작업은 정의상 비동기적으로 완료되므로 당신이 직관적으로 기대하거나 원하는 대로 blocking 동작을 수행하지 않습니다.

// ajax(..) is some arbitrary Ajax function given by a library
var data = ajax( "http://some.url.1" );

console.log( data );
// Oops! `data` generally won't have the Ajax results

 아마 당신은 표준 Ajax 요청들이 동기적으로 완료되지 않는다는 것을 알고 있을 것입니다. 즉, ajax(...) 함수는 data 변수에 할당하기 위해 반환할 값이 아직 없습니다. 만약 ajax(...) 함수가 응답이 올 때까지 block할 수 있다면, data = .. 할당은 잘 작동할 것입니다.

 그러나 이것은 Ajax가 동작하는 방식이 아닙니다. 우리는 지금(now) 비동기 Ajax 요청을 보냈고, 나중(later)까지 결과를 돌려받지 못할 것입니다.

 now부터 later까지 기다릴 수 있는 가장 간단한 방법은 흔히 callback 함수라 불리는 함수를 사용하는 것입니다.

// ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", function myCallbackFunction(data){

	console.log( data ); // Yay, I gots me some `data`!

} );

Warning: 당신은 아마 Ajax의 동기식 요청이 가능하다는 것을 들었을 겁니다. 기술적으로 그렇지만 이는 브라우저 UI를 block하고 사용자가 상호 작용을 하지 못하도록 막기 때문에 어떤 상황에서라도 절대 그렇게 해서는 안됩니다. 이는 좋지 않은 생각이며 항상 피해야 합니다.

 당신이 동의하지 않고 항의하기 전에, 아니, 콜백의 혼란을 피하고 싶은 당신의 욕망은 blocking이며 동기식인 Ajax를 차단하기 위한 정당화가 아니다.

 예를 들어, 다음 코드를 봅시다.

function now() {
	return 21;
}

function later() {
	answer = answer * 2;
	console.log( "Meaning of life:", answer );
}

var answer = now();

setTimeout( later, 1000 ); // Meaning of life: 42

 여기 프로그램에 두 chunk가 있고 하나는 지금(now) 실행되고 하나는 나중에(later) 실행됩니다. 이 두 chunk가 무엇인지 아주 분명하게 나타내 봅시다.

Now:

function now() {
	return 21;
}

function later() { .. }

var answer = now();

setTimeout( later, 1000 );

Later:

answer = answer * 2;
console.log( "Meaning of life:", answer );

 now chunk는 프로그램을 실행하자마자 곧 바로 실행됩니다. 그러나 setTimeout(...)은 나중에 일어나도록 이벤트를 맞춰놓아서, 지금으로부터 1,000ms 후에 later() 함수의 내용이 실행될 것입니다.

 코드의 일부를 function으로 감싸고 어떤 이벤트(타이머, 클릭, Ajax 응답, …)에 반응하여 실행되야 한다고 명시할 때마다 코드의 later chunk를 생성하여 프로그램에 비동기를 발생시킵니다.

Async Console

 console.* 메서드의 작동 방식에 대한 사양이나 요구 사항은 없습니다. 공식적인 JavaScript의 일부가 아니라 호스팅 환경에 의해 JS에 추가됩니다.

 따라서 서로 다른 브라우저와 JS 환경이 원하는 대로 작동하므로 때때로 혼란을 야기할 수 있습니다.

 특히 console.log(..)가 실제로 주어진 내용을 즉시 출력하지 않는 특정 브라우저 또는 조건이 있습니다. 주된 원인은 I/O가 매우 느리고 많은 프로그램(JS 뿐만 아닌)을 차단하기 때문입니다. 그래서 page/UI 관점에서는 브라우저가 console을 백그라운드에서 I/O 비동기로 다루는 것이 성능적으로 더 좋을 수 있습니다.

 이것을 관찰할 수 있는 매우 일반적이지 않은 시나리오입니다.

var a = {
	index: 1
};

// later
console.log( a ); // ??

// even later
a.index++;

 일반적으로 a객체가 정확히 console.log(..)문에서 스냅샷되어 { index: 1 }을 출력하는 것과 그리고 a.index++이 실행되거나 바로 직후에 a의 출력과는 다른 것을 수정하길 기대합니다.

 거의 대부분, 앞 코드는 개발자 도구의 콘솔에서 기대한 것과 같은 결과물을 보여줍니다. 그러나, 같은 코드가 console I/O를 백그라운드로 미루는 브라우저에서 실행된다면, 객체가 브라우저 콘솔에서 표현되기 전에 a.index++가 미리 실행돼서 결국 { index: 2 }를 보여주기도 합니다.

 console I/O가 정확히 어떤 조건에서 연기될 것인지, 또는 관찰 가능한 여부인지에 따라 이는 움직이는 대상입니다. 단지 디버깅 시 console.log(..) 이후에 객체가 수정된 문제가 발생할 경우엔 I/O에서 비동기성의 가능성이 있다는 것을 알기 바랍니다.

Note: 이 드문 시나리오를 우연히 마주하는 경우, console 결과에 의존하는 것보다 JS 디버거의 breakpoint를 사용하는 것이 최선의 선택입니다. 차선책은 JSON.stringfy(..) 같은 메서드로 문제의 객체를 문자열로 직렬화하여 스냅샷을 강제로 생성하는 것입니다.

Event Loop

 (아마도 충격적인) 주장을 해봅시다. 비동기 JS 코드를 분명히 허용했음에도 불구하고 최근(ES6)까지 JavaScript 자체는 비동기에 대한 직접적인 개념을 내장한적이 없습니다.

 What!? 미친 주장 처럼 들리지 않나요? 실제로, 이것은 꽤 사실입니다. JS 엔진 자체는 요청을 받고 주어진 순간에 프로그램의 한 chunk를 실행하는 것 이상의 어떠한 행동도 수행하지 않습니다.

  저는 “요청을 받고”라 했습니다. 근데 누구에게 받을까요? 그것이 중요한 부분입니다!

 JS 엔진은 독립된 환경에서 실행하지 않습니다. hosting 환경에서 실행되는데, 이것은 대부분의 개발자에게 일반적인 웹 브라우저 입니다. 지난 몇년 동안, JS는 Node.js같은 것들을 통해 브라우저를 넘어 서버 처럼 다른 환경으로 확장되었습니다. 사실, 자바스크립트는 요즘 로봇에서 전구에 이르기까지 모든 종류의 장치에 내장되어 있습니다. (독점적이진 않습니다.)

 그러나 이러한 모든 환경의 한 가지 공통적인 “스레드”는 그 안에 “이벤트 루프”라고 불리는 JS 엔진을 호출하는 각 순간에 프로그램의 여러 청크의 실행을 처리하는 메커니즘이 있다는 것입니다.

 즉, JS 엔진은 고유한 시간 감각이 없었으나, 그 대신 JS의 임의 스니펫에 대한 on-demand 실행 환경이 됐습니다. 항상 “events(JS 코드 실행)”을 스케쥴링 하는 것은 surrounding 환경입니다.

 예를 들자면, 서버로부터 데이터를 받아오는 Ajax 요청을 보낼 때, 흔히 콜백 함수안에 응답을 처리하기 위핸 코드를 작성하고 JS 엔진은 hosting 환경에게 “일단 실행을 중단할 테지만, 네트워크로부터 요청을 받는 언제든지 이 함수를 다시 호출해줘”라고 말합니다.

 그러면 브라우저는 네트워크로부터 오는 응답을 대기하고, 무언가 오면 콜백 함수가 실행될 수 있도록 이벤트 루프에 넣어 스케쥴링 합니다.

 그래서 event loop가 무엇인가요?

 먼저 가짜 코드를 통해 개념화해 보겠습니다.

// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = [ ];
var event;

// keep going "forever"
while (true) {
	// perform a "tick"
	if (eventLoop.length > 0) {
		// get the next event in the queue
		event = eventLoop.shift();

		// now, execute the next event
		try {
			event();
		}
		catch (err) {
			reportError(err);
		}
	}
}

 물론, 개념을 위해 굉장히 단순화된 의사 코드입니다. 하지만, 이해를 돕기엔 충분합니다.