learning node book, chapter 5
TRANSCRIPT
CH 5. 제어 흐름, 비동기 패턴, 예외 처리Learning Node
김지훈
Promise
Node의 초기 비동기 구현
success와 error 두 개의 이벤트만 가짐
Promise
var File = require('file'); var promise = File.read('mydata.txt'); promise.addCallback(function (data) { // process data }); promise.addErrback(function (err) { // deal with error })
– Ryan Dahl -
‘promise대신 최종 인수 콜백을 사용하도록 하고, 보다 나은 추상화 계층을 구축하는 것은 사용자 라이브러리에
맡기도록 하겠다.’
Promise 의 제거Node 0.1.30
최종 인수 콜백
모든 비동기 메서드는 마지막 인수로 콜백 함수를 받음
콜백의 첫번째 인수는 항상 error 개체
Last Argument Callbacks
최종 인수 콜백 구현 예시var obj = function() { }; obj.prototype.doSomething = function(arg1, arg2_) {
var arg2 = typeof(arg2_) === 'string' ? arg2_ : null;
var callback_ = arguments[arguments.length - 1]; callback = (typeof(callback_) == 'function' ? callback_ : null); if (!arg2)
return callback(new Error('second argument missing or not string')); callback(arg1); } var test = new obj(); test.doSomething(‘test', ‘this’, function(err,value) {
if (err) throw err;
console.log(value);
});
최종 인수 콜백의 역할
마지막 인수가 함수임을 보증
오류가 발생한 경우 Node Error를 생성하여 반환
오류가 발생하지 않으면 메서드의 결과를 전달하여 콜백 호출
중첩 콜백!var fs = require('fs'); try { fs.readFile('./apples2.txt','utf8', function(err,data) { if (err) throw err; var adjData = data.replace(/[A|a]pple/g,'orange');
fs.writeFile('./oranges.txt', adjData,function(err) { if (err) throw err
}); }); } catch(err) { console.error(err);
}
Nested Callbacks
// get list of files fs.readdir('./data/', function(err, files) {
// for each file
files.forEach(function(name) {
// modify contents fs.readFile('./data/' + name,'utf8', function(err,data) {
if (err) throw err; var adjData = data.replace(/somecompany\.com/g,'burningbird.net');
// write to file fs.writeFile('./data/' + name, adjData, function(err) {
if (err) throw err;
// log write writeStream.write('changed ' + name + '\n', 'utf8', function(err) {
if(err) throw err;
});
});
});
});
}); 콜백 스파게티, 죽음의 피라미드 (Pyramid of Doom)
changed data1.txt
changed data3.txt
changed data5.txt
changed data2.txt
changed data4.txt
!changed data3.txt
changed data1.txt
changed data5.txt
changed data2.txt
changed data4.txt
!changed data1.txt
changed data3.txt
changed data5.txt
changed data4.txt
changed data2.txt
콜백이 호출되는 순서도 무작위
서드파티 모듈
Node는 기본적인 콜백 기능만 제공
보다 높은 추상화는 서드파티 모듈에 의존해야 함
Step, Async
Step
간단한 서드파티 비동기 모듈
실행할 함수 리스트를 넘겨주면 순차적으로 실행해줌
var fs = require('fs'), Step = require('step'); try { Step ( function readData() { // 비동기 함수 fs.readFile('./data/data1.txt', 'utf8', this); }, function modify(err, text) { // 동기 함수 if (err) throw err; return text.replace(/somecompany\.com/g,'burningbird.net'); }, function writeData(err, text) { // 비동기 함수 if (err) throw err; fs.writeFile('./data/data1.txt', text, this); } ); } catch(err) { console.error(err); }
콜백 대신에 this를 전달 첫 번째 함수를 제외한 모든 함수는 첫 번째 매개변수로 error를 요구 에러는 예외로 처리
…생략… try { Step ( function readDir() { fs.readdir(_dir, this); }, function readFile(err, results) { if (err) throw err; files = results; var group = this.group(); results.forEach(function(name) { fs.readFile(_dir + name, 'utf8', group()); }); }, function writeAll(err, data) { if (err) throw err; for (var i = 0; i < files.length; i++) { var adjdata = data[i].replace(/somecompany\.com/g,'burningbird.net'); fs.writeFile(_dir + files[i], adjdata, 'utf8',this); } } ); } catch(err) { console.log(err); } ! 결과가 배열로 그룹화 되어 전달됨
병렬로 실행하기var fs = require('fs'), Step = require('step'), files; try { Step ( function readFiles() { fs.readFile('./data/data1.txt', 'utf8',this.parallel()); fs.readFile('./data/data2.txt', 'utf8',this.parallel()); fs.readFile('./data/data3.txt', 'utf8',this.parallel()); }, function writeFiles(err, data1, data2, data3) { if (err) throw err; data1 = data1.replace(/somecompany\.com/g,'burningbird.net'); data2 = data2.replace(/somecompany\.com/g,'burningbird.net'); data3 = data3.replace(/somecompany\.com/g,'burningbird.net'); fs.writeFile('./data/data1.txt', data1, 'utf8',this.parallel()); fs.writeFile('./data/data2.txt', data2, 'utf8', this.parallel()); fs.writeFile('./data/data3.txt', data3, 'utf8', this.parallel()); } ); } catch(err) { console.log(err); }
this.parallel()이 각 함수의 순서를 보장
Async
더 다양한 기능을 제공
기존 콜백구조와 매우 유사한 코드 스타일
var fs = require('fs'), async = require('async'); try { async.waterfall([ function readData(callback) { fs.readFile('./data/data1.txt', 'utf8', function(err, data){ callback(err,data); }); }, function modify(text, callback) { var adjdata=text.replace(/somecompany\.com/g,'burningbird.net'); callback(null, adjdata); }, function writeData(text, callback) { fs.writeFile('./data/data1.txt', text, function(err) { callback(err,text); }); } ], function (err, result) {//에러 발생시 처리를 중단하고 완료 콜백 호출 if (err) throw err; console.log(result); }); } catch(err) { console.log(err); }
async.waterfall
……
function readData(stats, file, callback) {
if (stats.isFile())
fs.readFile(_dir + file, 'utf8', function(err, data){
callback(err,file,data);
});
},
function modify(file, text, callback) {
var adjdata=text.replace(/somecompany\.com/g,'burningbird.net');
callback(null, file, adjdata);
},
……
async.waterfall([작업 함수 배열, …], 완료 콜백);
파라메터 전달 흐름
callback의 첫번째 인자는 error개체, 각 함수의 마지막 인자는 callback개체
async.parallelasync.parallel({
작업 이름 : 작업 함수,
… 반복 …
}, 완료 콜백);
넘겨준 작업 함수들을 모두 동시에 실행
모든 함수의 동작이 끝나길 기다렸다가 완료 콜백 호출
var fs = require('fs'), async = require('async');!
try { !
! async.parallel({!
! ! data1 : function (callback) {!
! ! ! fs.readFile('./data/data1.txt', 'utf8', function(err, data){!
! ! ! ! callback(err,data); !
! ! ! });!
! ! },! ! data2 : function (callback) {!
! ! ! fs.readFile('./data/data2.txt', 'utf8', function(err, data){ !
! ! ! ! callback(err,data);!
! ! ! });!
! ! },!
! ! data3 : function readData3(callback) { !
! ! ! fs.readFile('./data/data3.txt', 'utf8', function(err, data){!
! ! ! ! callback(err,data); !
! ! ! });!
! ! },!
! }, function (err, result) { // 완료 콜백!! ! if (err) throw err;!
! ! console.log(result); !
! });!
} catch(err) {!
! console.log(err); !
}
입력
data1.txt : apple
data2.txt : oranges
data3.txt : peaches
!
결과
{ data1: ‘apples\n’, data2: 'oranges\n', data3: 'peaches\n' }
{ 작업명 : 각 작업별 결과, … }
Node 스타일동기보다는 비동기 함수를 사용
2칸 들여쓰기 사용
세미콜론을 사용 / 사용하지 마라
작은 따옴표를 사용
여러 변수 선언시 단일 var 키워드 사용 / 여러 var 키워드 사용
상수는 모두 대문자
변수는 camel case
완전 항등 연산자(===) 사용
함수에 이름 붙이기
라인길이 80자 이하
중괄호는 중괄호를 필요로 하는 것과 같은 줄에서 시작