14.4.1 1보 전진과 2보 후퇴?
const fs = require('fs');
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);
})
})
}
function ptimeout(delay) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, delay)
})
}
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 {
// 세번째 이후 매개변수 : 타이머가 만료되고 func에 전달되는 추가적인 매개변수들입니다.
setTimeout(iterate, 0, x.value);
}
}
})();
}
function* theFuntureIsNow() {
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(theFuntureIsNow);
“그냥 세 개의 파일을 동시에 읽으면 더 효율적이지 않나?” 예리한 질문을 하는 독자도 있을 겁니다.
그 질문에 대한 답은 문제에 따라, 자바스크립트 엔진에 따라, 운영체제에 따라, 파일시스템에 따라 크게 다를 수 있습니다.
하지만 복잡한 부분은 잠시 미뤄두고, 세 파일을 읽는 순서는 상관이 없다는 것, 그리고 설령 세 파일을 동시에 읽었다 한들 과연 효율적일지는 의문스럽다는 점을 상기합시다.
theFutureIsNow 함수를 이런 식으로 만든 것은, 이 방법이 이해하기 쉽고 단순해 보였기 때문입니다.
Promise
에는 all
메서드가 있습니다.
이 메서드는 배열로 받은 프로미스가 모두 완료될 때 완료되며, 가능하다면 비동기적 코드를 동시에 실행합니다.
theFutureIsNow 함수가 Promise.all을 사용하도록 수정하기만 하면 됩니다.
const fs = require('fs');
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);
})
})
}
function ptimeout(delay) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, delay)
})
}
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 {
// 세번째 이후 매개변수 : 타이머가 만료되고 func에 전달되는 추가적인 매개변수들입니다.
setTimeout(iterate, 0, x.value);
}
}
})();
}
function* theFuntureIsNow() {
const data = yield Promise.all([
nfcall(fs.readFile, 'a.txt'),
nfcall(fs.readFile, 'b.txt'),
nfcall(fs.readFile, 'c.txt'),
])
yield ptimeout(60*1000);
yield nfcall(fs.writeFile, 'd.txt', data[0] + data[1] + data[3]);
}
grun(theFuntureIsNow);
Promise.all
이 반환하는 프로미스에는 매개변수로 주어진 각 프로미스의 완료 값이 배열에 들어있었던 순서대로 들어있습니다.
c.txt를 a.txt보다 먼저 읽더라도 data[0]에는 a.txt의 내용이, data[2]에는 c.txt의 내용이 들어 있습니다.
Promise.all
은 편리한 도구이고 알아두면 좋지만, 이 섹션에서 가장 중요한 것은 Promise.all이 아닙니다.
이 섹션에서 가장 중요한 것은 프로그램에서 어떤 부분을 동시에 실행할 수 있고 어떤 부분은 동시에 실행할 수 없는지를 판단하는 것이어야 합니다.
이 예제에서는 파일을 읽는 것과 타임아웃이 동시에 실행되더라도 상관없습니다.
어떤 부분을 동시에 실행할 수 있고 어떤 부분은 동시에 실행할 수 없는지를 판단하는 것은 문제에 따라 다릅니다.
세파일을 읽은 다음에 60초를 기다리고 그 다음에 병합 결과를 파일에 저장하는 것이 중요하다면 그 답은 이미 예제에 들어있습니다.
반면 세 파일을 읽는 것과 무관하게 60초 이상이 흐른 다음 네 번째 파일에 결과를 저장하는 것이 중요하다면 타임아웃을 Promise.all로 옮기는 편이 좋을 겁니다.