비동기와 동기적인 코드란.
동기적인 코드 - 현재 실행 중인 코드가 완료 된 후 다음 코드를 실행하는 방식.
비동기적인 코드 - 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
cpu의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드고 계산식이 복잡해서 cpu가 계산하는데 시간이 많이 필요한 경우라하더라도 동기적인 코드입니다.
반면
setTimeOut처럼 사용자의 요청에 의해 특정 시간이 경과되기 전까지 어떤 함수의 실행을 보류한다거나
addEventListener처럼 사용자의 직접적인 개입이 있을 때 비로소 어떤 함수를 실행하도록 대기한다거나
XMLHttpRequest처럼 웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에 대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기하는 등, 별도의 요청, 실행 대기, 보류 등과 관련된 코드가 비동기적인 코드입니다.
비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠지기 쉽습니다.
최근에 콜백 지옥을 벗어날 수 있는 여러 방법들이 등장하고 있는데 제일 최신 방법을 먼저 알아보자면,
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(name);
}, 500);
});
};
var coffeeMaker = async function () {
var coffeeList = "";
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? "," : "") + (await addCoffee(name));
};
await _addCoffee("에스프레소");
console.log(coffeeList);
await _addCoffee("아메리카노");
console.log(coffeeList);
await _addCoffee("카페모카");
console.log(coffeeList);
await _addCoffee("카페라떼");
console.log(coffeeList);
};
coffeeMaker();
//출력 결과.
//에스프레소
//에스프레소,아메리카노
//에스프레소,아메리카노,카페모카
//에스프레소,아메리카노,카페모카,카페라떼
//500ms마다 하나씩 추가된 커피가 출력됨.
위의 코드는 최신 문법(ES2017(ES8))에서 추가된 async/ await
를 사용한 비동기 코드입니다.
가독성이 뛰어나고 작성법도 간단합니다.
비동기 작업을 수행하고자 하는 함수 앞에 async로 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await
를 표기하여 뒤의 내용을 Promise
로 자동 전환하고, 해당 내용이 resolve
된 이후에야 다음으로 진행합니다.
async/await
는 promise
기반으로 사용되고 promise
를 사용하지만 then,catch
메소드를 사용해서 컨트롤을 하는 것이 아닌 동기적코드처럼 반환 값을 변수에 할당하여 작성할 수 있게 도와주는 문법입니다.
즉 Promise
의 then
과 흡사한 효과를 얻을 수 있습니다.
이전에는 어떤 방식으로 비동기처리를 했는지 보자면,
//ES6 Promise
new Promise(function(resolve) {
setTimeout(function() {
var name = '에스프레소';
console.log(name);
resolve(name);
}, 500);
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 아메리카노';
console.log(name);
resolve(name);
}, 500);
});
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 카페모카';
console.log(name);
resolve(name);
}, 500);
});
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 카페라떼';
console.log(name);
resolve(name);
}, 500);
});
});
//ES6 Promise
var addCoffee = function(name) {
return function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var newName = prevName ? prevName + ', ' + name : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
위 2개는 같은 내용이지만 반복적인 내용을 함수화해서 더욱 짧게 표현한 것 입니다.
ES6의 Promise
는 함수를 인자로 받으며 인자로 들어온 함수는 다시 resolve
와 reject
2개의 함수를 인자로 받게 됩니다.
resolve
는 비동기 처리 성공 시 호출되며,reject
는 비동기 처리 실패 시 호출됩니다.
promise의 인자로 받는 콜백함수는 호출할 때 바로 실행되지만 그 내부에 resolve 또는 reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then 또는 catch로 넘어가지 않습니다.
따라서 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능합니다.
//ES6 Generator
var addCoffee = function(prevName, name) {
setTimeout(function() {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
var coffeeGenerator = function*() {
var espresso = yield addCoffee('', '에스프레소');
console.log(espresso);
var americano = yield addCoffee(espresso, '아메리카노');
console.log(americano);
var mocha = yield addCoffee(americano, '카페모카');
console.log(mocha);
var latte = yield addCoffee(mocha, '카페라떼');
console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
ES6의 Generator
를 사용한 방법입니다.
6번째 줄에 함수 뒤에 *
이 붙은 함수가 Generator
함수 입니다.
Generator
함수를 실행하면 Iterator
가 반환되는데,Iterator
는 next
라는 메서드를 갖고 있습니다.
이 next
메서드를 호출하면 Generator
함수 내부에서 가장 먼저 등장하는 yeild
에서 함수의 실행을 멈추고 이후 다시 next
메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그 다음 yeild
에서 함수의 실행을 멈춥니다.
그러니까 비동기작업이 완료되는 시점마다 next
메서드를 호출해준다면 Generator
함수 내부의 소스가 위에서부터 아래로 순차적으로 진행됩니다.
출처 - 코어자바스크립트, MDN