function spider(url, callback) {
const filename = utilities.urlToFilename(url);
fs.exists(filename, exists => { //[1]
if(!exists) {
console.log(`Downloading ${url}`);
request(url, (err, response, body) => { //[2]
if(err) {
callback(err);
} else {
mkdirp(path.dirname(filename), err => { //[3]
if(err) {
callback(err);
} else {
fs.writeFile(filename, body, err => { //[4]
if(err) {
callback(err);
} else {
callback(null, filename, true);
}
});
}
});
}
});
} else {
callback(null, filename, false);
}
});
}
Javascript 자체를 사용하여 비동기를 제어
3.2.1 일반 규칙
// else 제거 전 - else 문 안에(들여쓰기+1) 로직이 들어간다.
if(err) {
callback(err);
} else {
// 다음 로직 수행
}
// else 제거 - 오류 발생 시에 반드시 return 필요
if(err) {
return callback(err);
}
// 다음 로직 수행
3.2.2 순차 실행
특정 복수의 로직들을 순차적으로 실행하는 패턴.
실행되어야 할 로직들이 고정(로직의 수, 로직의 내용)일 경우 사용가능하지만, 실전에서는 부족함
function asyncOperation(callback) {
process.nextTick(callback);
}
function task1(callback) {
asyncOperation(() => {
task2(callback);
});
}
function task2(callback) {
asyncOperation(() => {
task3(callback);
});
}
function task3(callback) {
asyncOperation(() => {
callback(); //finally executes the callback
});
}
task1(() => {
//executed when task1, task2 and task3 are completed
console.log('tasks 1, 2 and 3 executed');
});
3.2.3 순차 반복 실행
동적인 개수의 작업을 순차적으로 반복 실행하는 패턴
function iterate(index) {
if(index === tasks.length) {
return finish();
}
const task = tasks[index];
task(function() {
iterate(index + 1); // 현재의 task가 종료되면 다음 task를 실행
});
}
function finish() {
console.log("all tasks are finished");
}
3.2.4 병렬 실행
주어진 작업들을 병렬로 실행하는 패턴.
node.js에서도 병렬 수행이 가능하지만, 멀티스레드 프로그래밍에서 사용하는 lock, mutex, semaphore, monitor 등을 사용하지 않는다.
하지만 병렬 작업 수행 시 아래와 같이 경쟁(race) 상황에 놓이는 것은 동일하다.
const tasks = [ /* tasks */ ];
let completed = 0;
tasks.forEach(task => { // 전체 tasks를 반복하며, task 수행(비동기)
task(() => {
if(++complieted === tasks.length) { // 각 task 종료 시 종료 조건을 체크하여 마지막에 종료된 task는 전체 작업을 종료
finish();
}
});
});
function finish() {
console.log("all tasks are finished");
}
위의 예에서 동시에 동일한 작업을 수행하는 경우를 커버하지 못하는데, 이는 전역적인 map, set 등으로 해결할 수 있다.
3.2.5 제한된 병렬 실행
3.2.4의 병렬 실행의 경우 동시에 실행 가능한 작업의 수는 무한대이다. 이것은 리소스에 대한 문제의 소지가 있기 때문에 제한이 필요하다.
const tasks = [ /* tasks */ ];
let concurrency = 2, running = 0, completed = 0, index = 0;
function next() {
while(running < concurrency && index < tasks.length) { // 현재 실행 중인 task의 개수에 따라 실행 여부 결정
task = tasks[index++];
task(() => {
if(completed === tasks.length) {
return finish();
}
completed++, running--;
next(); // task 작업 종료 후 다음 task 실행
});
running++;
}
}
next();
function finish() {
console.log("all tasks are finished");
}
3.2.6 제한된 병렬실행2 - Queue 사용
병렬 실행을 통제하기 위한 수단으로 Queue를 사용하는 패턴을 사용할 수 있다.
전체 작업을 일반화할 수 있고, 동적으로 작업이 상시 발생하는 경우에 가장 적합한 패턴이라고 할 수 있다.
module.exports = class TaskQueue {
constructor (concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
pushTask (task) { // 동적으로 queue에 작업을 추가할 수 있다.
this.queue.push(task);
this.next();
}
next() {
while (this.running < this.concurrency && this.queue.length) {
const task = this.queue.shift(); // 대기 중인 첫 번째 task
task (() => {
this.running--;
this.next();
});
this.running++;
}
}
};
https://www.npmjs.com/package/async
3.3.1 순차 실행
async.serise(tasks, [callback]);
async.waterfall(tasks, [callback]);
async.eachSeries(tasks, [callback]);
3.3.2 병렬 실행
async.each(tasks, [callback]);
3.3.3 제한된 병렬실행
eachLimit()
mapLimit()
paralleLimit()
queue()
cargo()