책 <러닝자바스크립트>를 기본으로 배운 자바스크립트 내용입니다.
목차
- Promise
- Promise 생성
- Promise의 메서드 체이닝(method chaining)
- Promise chain
- 제너레이터
- 비동기적 프로그래밍 요약
<비동기적 프로그래밍(2) - 프로미스, 제너레이터>
Promise
- 프로미스는 현재에는 당장 얻을 수 없지만 가까운 미래에는 얻을 수 있는 어떤 데이터에 접근하기 위한 방법을 제공한다. 당장 원하는 데이터를 얻을 수 없다는 것은 데이터를 얻는데까지 지연시간이 발생하는 경우를 말한다. 때문에 Non-blocking코드를 지향하는 자바스크립트에서는 비동기 처리가 필수적이다.
- 프로미스 기반 비동기적 함수를 호출하면 그 함수는 Promise인스턴스를 반환한다.
- 프로미스는 완전히 resolve(성공), reject(실패) 딱 2가지 뿐이다.
- 프로미스는 콜백을 예측 가능한 패턴으로 사용할 수 있게 한다.
- 프로미스는 객체이므로 어디든 전달할 수 있다.
- 프로미스는 체인으로 연결할 수 있다는 장점이 있다.
- 콜백의 단전임 콜백 헬을 보완하기 위해서 만들어진 것이 프로미스이다. 콜백 자체를 대체하는 것이 아니라 프로미스를 사용함으로써 안전하고 관리하기 쉬운 코드를 만들 수 있다.
findUser(1).then(function(user) {
console.log("user:", user);
});
function findUser(id) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("waited 0.1 sec.");
const user = {
id: id,
name: "User" + id,
email: id + "@test.com"
};
resolve(user);
}, 100);
});
}
//실행 결과
waited 0.1 sec.
user: {id: 1, name: "User1", email: "1@test.com"}
위 코드는 콜백 함수를 인자로 넘기는 대신에 Promise객체를 생성하여 리턴하였고, 호출부에서는 리턴받은 Promise객체에 then()메서드를 호출하여 결과값을 가지고 실행할 로직을 넘겨주고 있다.
콜백 함수를 통해 비동기 처리를 하던 기존 코드와 가장 큰 차이점은 함수를 호출하면 Promise 타입의 결과값이 리턴되고, 이 결과값을 가지고 다음에 수행할 작업을 진행한다는 것이다.
따라서 기존 스타일보다 비동기 처리 코드임에도 불구하고 마치 동기 처리 코드 처럼 코드가 읽히기 때문에 좀 더 직관적으로 느껴지게 된다.
Promise 생성
- Promise는 객체는 new 키워드와 생성자를 통해서 생성할 수 있는데 이 생성자는 함수를 인자로 받는다. 그리고 이 함수 인자는 reslove와 reject라는 2개의 함수형 파라미터를 가진다.
- 일반적으로 resolve() 함수의 인자로는 미래 시점에 얻게될 결과를 넘겨주고, reject() 함수의 인자로는 미래 시점에 발생할 예외를 넘겨준다.
- 정상적인 인자를 넘긴 경우 then() 메서드가 호출되고, 비정상적인 인자를 넘긴 경우 catch() 메서드가 호출된다.
# 프로미스 생성해 변수에 할당
const promise = new Promise(function(resolve, reject) { ... } );
# 프로미스 생성해 함수의 리턴값으로 사용
function countdown(seconds){
return new Promise(function(resolve, reject) => { ... } );
}
# 프로미스 사용
countdown(5)
.then(result => console.log("성공:", result))
.catch(error => console.log("실패:", error));
Promise의 메서드 체이닝(method chaining)
then()과 catch() 메서드는 또 다른 Promise 객체를 리턴한다. 그리고 이 Promise 객체는 인자로 넘긴 콜백 함수의 리턴값을 다시 then()과 catch() 메서드를 통해 접근할 수 있도록 해준다.
간단히 말해, then()과 catch() 메서드는 마치 사슬처럼 계속 연결하여 연쇄적으로 호출을 할 수 있다.
#Promise의 method chaining 예제
fetch("https://jocoma.tistory.com/")
.then(response => response.json())
.then(post => post.userId)
.then(userId => "url" + userId)
.then(url => fetch(url))
.then(response => response.json())
.then(user => console.log("user:", user))
.catch(error => console.log("error:", error));
Promise chain
- 프로미스는 체인으로 연결할 수 있다
- 프로미스가 완료되면 다른 프로미스를 반환하는 함수를 즉시 호출할 수 있다.
const p1 = new Promise(function(resolve, reject){
resolve(1);
})
const p2 = new Promise(function(resolve, reject){
resolve(3);
})
p1.then(function(data){
console.log(data);
return p2;
})
.then(function(data){
console.log(data)
})
제너레이터
제너레이터는 원래 동기적인 성격이지만, 프로미스와 결합하면 비동기 코드를 효율적으로 관리 할 수 있다.
//노드와 오류 우선 콜백을 프로미스로 변환
function nfcall(f, ...args) {
return new Promise(function(resolve, reject){
f.call(null, ...args, function(err,...args) {
if(err) return reject(err);
resolve(args.length<2 ? args[0] : args);
});
});
}
// setTimeout과 같은 기능에 오류 우선 콜백 패턴을 따르는 ptimeout 함수를 만들어 이용
function ptimeout(delay) {
return new Promise(function(resolve, reject){
setTimeout(resolve, delay);
});
}
//제너레이터 실행기 : 제너레이터와 호출자의 통신을 관리하고, 비동기적 호출 처리하는 grun 함수 만들어 이용
function grun(g) {
const it = g();
(function iterate(val){
const x = it.next(val);
if(!x.done) {
if(x.value instanceof Promise) {
x.value.then(iterate).catch(err => it.throw(err));
}else{
setTimeout(iterate,0,x.value);
}
}
})();
}
//비동기적이지만 마치 동기적 코드인것처럼 사람이 생각했을 법한 방법을 그대로 사용가능
function* theFutureIsNow() {
const dataA = yield nfcall(fs.readFile, 'a.txt');
const dataB = yield nfcall(fs.readFile, 'b.txt');
const dataC = yield nfcall(fs.readFile, 'c.txt');
yield ptimeout(60*1000);
yield nfcall(fs.writeFile, 'd.txt', dataA+dataB+dataC);
}
//실행
grun(theFutureIsNow);
※제너레이터 실행기는 다음 코드를 참고하도록 한다.
<비동기적 프로그래밍 요약>
- 자바스크립트의 비동기적 실행은 콜백을 통해 이루어진다.
- 프라미스를 콜백 대신 사용할 수 있는 건 아니다. 프라미스 역시 콜백을 사용한다.
- 프라미스는 콜백이 여러 번 호출되는 문제를 해결했다.
- 콜백을 여러 번 호출해야 한다면 이벤트와 결합하는 방법을 생각할 수 있다(프라미스도 함께 쓸 수 있다).
- 프라미스는 반드시 결정된다는(성공 또는 실패한다는) 보장은 없다. 프라미스에 타임아웃을 걸면 이 문제가 해결된다.
- 프라미스는 체인으로 연결할 수 있다.
- 프라미스와 제너레이터 실행기를 결합하면 비동기적 실행의 장점을 그대로 유지하면서도 동기적인 사고방식으로 문제를 해결할 수 있다.
- 제너레이터를 써서 동기적인 사고방식으로 문제를 해결할 때는 프로그램의 어느 부분을 동시에 실행할 수 있는지 잘 살펴야 한다. 동시에 실행할 수 있는 부분은 Promise.all을 써서 실행하면된다.
- 제너레이터 실행기를 쓰면 예외 처리도 익숙한 방식으로 할 수 있다.비동기적 실행을 자바스크립트에서 처음 접했다면 막막하고 어려울 수 있지만, 최신 자바스크립트 프로젝트에서는 비동기적 실행이 아주 많이 사용된다.