본문 바로가기

Node.js/5. 외부모듈

5-3 async 모듈 (비동기소스를 동기식으로 바꿔주자.)

var fs = require('fs');
  
var file1_path = './test1.txt';
var file2_path = './test2.txt';
var file3_path = './test3.txt';

fs.readFile(file1_path, function(err, content_file1) {
	fs.readFile(file2_path, function(err, content_file2) {
	    fs.writeFile(file3_path, content_file1 + content_file2, function(err) {
		if (err) throw err;
		console.log('end.');
	    });
	});
});
해당 소스는 파일1과 파일2의 데이터를 파일3에 쓰는 예제 입니다. 비동기형식의 node 에서는 이렇듯 코드 중첩이 계속 일어납니다. 만일 여기에 if 문이 하나라도 들어가면 코드는 두배로 늘어나며 코드 일긱도 불편해 집니다. 

var fs = require('fs');
  
var file1_path = './test1.txt';
var file2_path = './test2.txt';
var file3_path = './test3.txt';

fs.readFile(file1_path, 'utf8', function(err, content_file1) {
fs.appendFile(file3_path, content_file1 , function(err) {
if (err) throw err;
console.log('file1 end.');
});
});

fs.readFile(file2_path, 'utf8', function(err, content_file2) {
    fs.appendFile(file3_path, content_file2, function(err) {
if (err) throw err;
console.log('file2 end.');
    });
});

해당 소스는 파일1과 파일2의 데이터를 파일3에 쓰는 예제 입니다. 비동기형식의 node 에서는 이렇듯 코드 중첩이 계속 일어납니다. 만일 여기에 if 문이 하나라도 들어가면 코드는 두배로 늘어나며 코드 일긱도 불편해 집니다. 

var fs = require('fs');
  
var file1_path = './test1.txt';
var file2_path = './test2.txt';
var file3_path = './test3.txt';

fs.readFile(file1_path, 'utf8', function(err, content_file1) {
	fs.appendFile(file3_path, content_file1 , function(err) {
		if (err) throw err;
		console.log('file1 end.');
	});
});

fs.readFile(file2_path, 'utf8', function(err, content_file2) {
    fs.appendFile(file3_path, content_file2, function(err) {
	if (err) throw err;
	console.log('file2 end.');
    });
});
만일 두개의 파일에서 각각의 로직을 하고 파일3에 써놓는 동기코드는 다음과 같을 겁니다. 읽기도 쉽고 직관적이지만 node 에선 위의 소스를 확신할수 없습니다. 만일 첫번째 로직이 시간이 엄청나게 걸린다면 두번째 로직이 먼저 발동되고 끝나버릴수도 있기 떄문입니다. 

이렇듯 비동기 로직은 우리가 생각한것과는 다르게 동작합니다.(너무 동기적 프로세스에 익숙해져 있기 때문이겠지요.)
비동기를 동기적으로 바꿔주는 모듈이 바로 async 모듈입니다. 


1. 먼저 설치 입니다. 0.9.0 버전이 설치되었습니다. 
npm install async

2. async 의 waterfall 예제 입니다. (일반적인 동기 스타일입니다.) 아래의 소스를 실행해 보면 waterfall() 안의 소스들이 차례로 실행되며 함수마다 콜백을 통하여 함수의 값을 다음 함수로 전달 할수 있습니다.
(만일 비동기로 처리한다면 중첩이 엄청나겠지요?)
var async = require('async');

async.waterfall([
    function(callback){
	console.log("1");
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
	console.log(arg1 + "  " + arg2);      	
	console.log("2");
        callback(null, 'three');
    },
    function(arg1, callback){
	console.log(arg1);
        console.log("3");
        callback(null, 'done');
    }
], function (err, result) {
   console.log(result);
   console.log("end"); 
});
3. 다음은 async의 parallel 입니다. parallel 는 당연히 동시에 일어납니다. 하지만 parallel() 안이 모두 끝날때 까지 다음으로 넘어가지 않습니다. 
var async = require('async');

async.parallel([
    function(callback){
        setTimeout(function(){
            callback(null, 'one');
        }, 200);
    },
    function(callback){
        setTimeout(function(){
            callback(null, 'two');
        }, 100);
    }
],

function(err, results){
    console.log(results);
});


async 에서 많이 사용하는 waterfull 과 parallel 을 살펴 봤습니다. 이 두개만 잘 사용해도 비동기로직을 동기로 변환하여 보다 쉽게 로직 표현을 할수 있습니다. 


마지막으로 맨 처음의 예제였던 파일을 읽은 다음 쓰는 예제를 async 로 변환한 소스 입니다. 

var fs = require('fs');
var async = require('async');
  
var file1_path = './test1.txt';
var file2_path = './test2.txt';
var file3_path = './test3.txt';

async.waterfall([
    function(callback){
	fs.readFile(file1_path, 'utf8', function(err, content_file1) {
			fs.appendFile(file3_path, content_file1 , function(err) {
				if (err) throw err;
				console.log('file1 end.');
			});
		});
        callback(null);
    },
    function(callback){
	fs.readFile(file2_path, 'utf8', function(err, content_file2) {
		    fs.appendFile(file3_path, content_file2, function(err) {
			if (err) throw err;
			console.log('file2 end.');
		    });
		});
        callback(null);
    }
], function (err, result) {
  
   console.log("end"); 
});