์ฌ์ด๋ ํ๋ก์ ํธ์์ Express๋ฅผ ์ค๋ ๊ธฐ๊ฐ ์ฌ์ฉํ์๋๋ฐ hapi ๊ฐ ์ข๋ค๋ ์๊ธฐ๋ฅผ ๋ฃ๊ณ ๋ hapi๋ฅผ ๋ง์ด ์ฌ์ฉํด์๋ค. Hapi๋ ๋จ์ํ๊ธด ํ์ง๋ง โ์ค์ ๋ง ๋ฃ์ผ๋ฉด ๋๋โ ๋จ์ํจ์ด๋ผ์ ์ค์ ์ ๋ค์ด๊ฐ๋ ์๊ณ ๊ฐ ๊ฝค ์ปธ๋ค. ์ต๊ทผ์๋ ํ ์ด ํ๋ก์ ํธ์์ API๋ฅผ ์์ฑํ๋๋ฐ ์๋ฌ ๋ฐ์ ์ฌ๋ถ์ ๋ฐ๋ผ์ {"ok": true}
ํ๋ ๋ฃ์ด์ฃผ๋ ์์
์ ์ค๋ง๊ฐ์ง ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค. express์ ๋ค๋ฅด๊ฒ ๋ฏธ๋ค์จ์ด์์ request, response์ ์ ๊ทผํ ์ ์๋ ํฌ์ธํธ๊ฐ ์๋์ ๋ง์ ๋ ๋ณต์กํ๊ฒ ๋๊ปด์ก๋ค. ๊ทธ๋ฌ๋ ์ค ์์ ์ ์ ์ ๋น๊ต๊ธ๋ก ๋ดค๋ koa๋ฅผ ์ดํด๋ดค๋๋ฐ ์ง๊ธ ํ์ํ ์ํฉ์ ๋ง๋ ๊ฒ ๊ฐ์ koa๋ก ๋ค์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ๋ง์์ ๋๋ ๊ตฌ์์ด ๋ง์์ ๊ฐ๋จํ ์๊ฐ๋ฅผ ์์ฑํ๋ค.
Koa๋ ES2015์ ๋ฌธ๋ฒ ์ค ํ๋์ธ ์ ๋๋ ์ดํฐ๋ฅผ ์ ๊ทน์ ์ผ๋ก ํ์ฉํ๊ณ ์๋ ์น ํ๋ ์์ํฌ๋ค. ๋ชจ๋ ์์ฒญ๊ณผ ์ฒ๋ฆฌ๋ฅผ ์ ๋๋ ์ดํฐ๋ฅผ ํ์ฉํด ํ์ดํ๋ผ์ธ์ ๋ง๋๋ ๊ฒ์ด ํน์ง์ด๋ฉฐ ๊ทธ ๋๋ถ์ ๊น๋ํ async ์ฝ๋๋ฅผ ์์ฝ๊ฒ ์์ฑํ ์ ์๋ค. Express ๋งํผ์ ์๋๋๋ผ๋ ๋ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณตํ๊ณ ์๊ณ , express์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ฏธ๋ค์จ์ด๋ thenify๋ co๋ก ๋ณํํด์ ํ์ฉํ ์ ์์ ๋งํผ ํ์ฅ์ฑ์ด ๋๋ค.
์ด ํฌ์คํธ๋ ์ ๋๋ ์ดํฐ๋ฅผ ๋จผ์ ์ดํด๋ณด๊ณ , ์ ๋๋ ์ดํฐ๋ฅผ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ co๋ฅผ ์ดํด๋ณธ ํ, KoaJS๋ฅผ ๊ฐ๋จํ๊ฒ ์ดํด๋ณด๋ ๊ฒ์ผ๋ก ๋ง๋ฌด๋ฆฌํ๋ค.
์ ๋๋ ์ดํฐ Generator
๋ค๋ฅธ ์ธ์ด์๋ ์ด๋ฏธ ์กด์ฌํ๊ณ ์๊ธฐ ๋๋ฌธ์ ํฌ๊ฒ ํน๋ณํ ๊ธฐ๋ฅ์ ์๋์ง๋ง ES6์์์ ๊ตฌํ์ ๊ฐ๋จํ ์ ๋ฆฌํ๋ ค๊ณ ํ๋ค.
์ผ๋ฐ์ ์ธ ํจ์์ ๊ฒฝ์ฐ, ๋งค ์คํ๋ง๋ค ๊ฐ์ ํ๋ฆ์ผ๋ก ๋ชจ๋ ์ฝ๋๋ฅผ ์คํํ์ง๋ง Generator ํจ์๋ ์คํ ์ค๊ฐ์์ ๊ฐ์ ๋ฐํํ ์ ์๊ณ , ๋ค๋ฅธ ์์
์ ์ฒ๋ฆฌํ ํ์ ๋ค์ ๊ทธ ์์น์์ ์ฝ๋๋ฅผ ์์ํ ์ ์๋ค. ์ด ์ ๋๋ ์ดํฐ๋ ๋ฐ๋ณต ํจ์ iterator๋ฅผ next()
๋ก ์ ๊ณตํ๊ณ ๊ฒฐ๊ณผ๋ฅผ value
๋ก, ์งํ ์ํฉ์ done
์ผ๋ก ํ์ธํ ์ ์๋ค.
๊ตฌ๊ตฌ๋จ์ ์ ๋๋ ์ดํฐ๋ก ์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
function* nTimesTable(n) {
for(var i = 1; i <= 9; i++) yield n * i;
}
์ ๋๋ ์ดํฐ๋ ์์ ๊ฐ์ด function* fnName(){}
์์ผ๋ก ์ ๋ฃ์ด ์ ์ธํ๋ค. ์ต๋ช
ํจ์์ ๊ฒฝ์ฐ๋ `function(){}` ์์ผ๋ก ์ ์ธํ๋ค.
์ด์ ์ดํฐ๋ ์ดํฐ(iterator)๋ฅผ nineTimesTable
์ ๋ฐํ ๋ฐ๋๋ค.
var nineTimesTable = nTimesTable(9);
์ดํฐ๋ ์ดํฐ๋ next()
๋ฅผ ํตํด ์คํํ ์ ์๋ค. ์ด ํจ์๋ก ์ค๋จํ ์์น์ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋๋ค.
var result = nineTimesTable.next();
console.log(result); // { value: 9, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 18, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 27, done: false }
// keep calling...
result = nineTimesTable.next();
console.log(result); // { value: 72, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 81, done: false }
result = nineTimesTable.next();
console.log(result); // { value: undefined, done: true }
๋งค ๋ฐ๋ณต ์คํ์์ value
๋ฅผ ๋ฐํํ์ง๋ง ๋์์ done
์ผ๋ก ํด๋น ํจ์๊ฐ yield
๊ฒฐ๊ณผ ์์ด ์ข
๋ฃ๋์๋์ง ํ์ธํ ์ ์๋ค. ๋ง์ง๋ง์ ๋ณ๋์ return ๊ฐ์ด ์๊ธฐ ๋๋ฌธ์ value
๊ฐ undefined
๊ฐ ๋๋ค.
์ด๋ฐ ์ดํฐ๋ ์ดํฐ์ ๋ฐํ ํน์ง์ ์ด์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด iterator๋ฅผ ํธ์ถํ๋ ํจ์๋ฅผ ์์ฑํ ์ ์๋ค.
function caller(iter) {
var result, value;
while(result = iter.next()) {
if(result.done) break;
value = result.value || value;
}
return value;
}
var result = caller(nTimesTable(3));
console.log(result); // 27
done
์ด true
๋ฅผ ๋ฐํํ ๋๊น์ง ํด๋น ์ดํฐ๋ ์ดํฐ๋ฅผ ์คํํด ๊ฒฐ๊ณผ๊ฐ์ ๊ฐ์ ธ์ค๋ caller
๋ฅผ ์์ฑํ๋ค. ๋ง์ฝ ๋งค ๋ฐ๋ณต์์ ํน์ ํจ์๋ฅผ ์คํํ๊ณ ์ถ๋ค๋ฉด ๋ค์์ฒ๋ผ ์์ฑํ ์ ์๋ค. ์์ ์์ฑํ nTimesTable
ํจ์๊ฐ ๋ ๋ง์ ๋ด์ฉ์ ๋ฐํํ๋๋ก ์์ ํ๋ค.
function * nTimesTable(n) {
for(var i = 1; i <= 9; i++) yield { n: n, i: i, result: n * i };
}
function caller(iter, func) {
var result, value;
while(result = iter.next()) {
if(result.done) break;
value = result.value || value;
if(func) func(value);
}
return value;
}
caller(nTimesTable(3), value => {
console.log('%d x %d = %d', value.n, value.i, value.result);
});
์์ ์์ฑํ caller
๋ ์ ๋๋ ์ดํฐ ๋ด์ yield์ ๋ํด์๋ ์ฒ๋ฆฌ๋ฅผ ํ์ง ๋ชปํ๋ค. ์ ๋๋ ์ดํฐ์์ ์ดํฐ๋ ์ดํฐ๋ฅผ ๋ฐํํ๊ณ ์งํ์ ์ค๋จํ์ ๋ ํด๋น ์ดํฐ๋ ์ดํฐ๋ฅผ ์ฒ๋ฆฌํด์ ๋ค์ ๋ฐํํด์ผ ํ๋ค. ๊ฒฐ๊ณผ๋ฅผ ๋ฃ๊ณ ๋ค์ ์งํํ ์ ์๋๋ก ์์ฑํด์ผ ํ๋ ๊ฒ์ด๋ค.
function* getAnimalInCage() {
yield "Wombat";
yield "Koala";
return "Kangaroo";
}
function* Cage() {
var cageAnimals = getAnimalInCage();
var first = yield cageAnimals;
var second = yield cageAnimals;
var third = yield cageAnimals;
console.log(first, second, third);
}
์ด Cage
์ ๋๋ ์ดํฐ๋ฅผ ์คํํ๋ฉด yield
๋ฅผ 3๋ฒ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ต์ข
console.log
๊ฐ ์ถ๋ ฅํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ๊น์ง 4๋ฒ์ ๊ฑธ์ณ ์คํ๋๋ค.
var cage = Cage();
var firstStop = cage.next();
// {value: iterator, done: false}
์ฒซ ๋ฒ์งธ yield
๊ฒฐ๊ณผ๊ฐ firstStop
์ ์ ์ฅ๋์๋ค. cageAnimals๋ ์์์ ์ฝ๋์์์ ๊ฐ์ด getAnimalInCage
์ ๋๋ ์ดํฐ๊ฐ ์์ฑํ ์ดํฐ๋ ์ดํฐ๋ค. ์ด ์ดํฐ๋ ์ดํฐ์ next()
๋ฉ์๋๋ก ๊ฐ์ ๋ฐ์ ํ, ๊ทธ ๊ฐ์ ๋ค์ first
๋ณ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ฐํํ๋ค.
var firstAnimal = firstStop.value.next();
// firstAnimal: {value: "Wombat", done: false}
var secondStop = cage.next(firstAnimal.value);
next
์ ์ธ์๊ฐ์ผ๋ก ์ฒซ ๊ฒฐ๊ณผ์ธ Wombat์ ๋ฃ์๋ค. ์ด์ ์ ๋ฉ์ท๋ ์์น์ธ ์ฒซ ๋ฒ์งธ yield๋ก ๋์๊ฐ ํจ์ ๋ด first
์๋ Wombat์ด ์ ์ฅ๋๋ค. ๋๋จธ์ง๋ ๋์ผํ๊ฒ ์งํ๋๋ค.
var secondAnimal = secondStop.value.next();
// secondAnimal: { value: 'Koala', done: false }
var thirdStop = cage.next(secondAnimal.value);
var thirdAnimal = thirdStop.value.next();
// thirdAnimal: { value: 'Kangaroo', done: true }
var lastStop = cage.next(thirdAnimal.value);
// Wombat Koala Kangaroo
๋ง์ง๋ง Kangaroo๋ yield๊ฐ ์๋ return์ด๊ธฐ ๋๋ฌธ์ done์ด true
๋ฅผ ๋ฐํํ๋ค. ์์ ์ง์ ํธ์ถํด์ ํ์ธํ ์ฝ๋๋ ๋ฐํํ๋ ๊ฐ์ด๋ ํธ์ถํ๋ ํํ๊ฐ ์ผ์ ํ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ฆ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํํ๋ก ๋ง๋ค ์ ์๋ค๋ ์๋ฏธ๋ค.
๋ค์์ catchEscapedAnimal()
๊ณผ getTodaysZookeeper()
ํจ์๋ฅผ ์ด์ฉํ Zoo
์ ๋๋ ์ดํฐ ์์๋ค.
function catchEscapedAnimal() {
return function(done) {
setTimeout(function() {
done(null, {name: 'Kuma', type: 'Bear'});
}, 1000);
};
}
function* getTodaysZookeeper() {
yield {status: 'loading'};
return {status: 'loaded', name: 'Edward'};
}
function* Zoo() {
var animal = yield catchEscapedAnimal();
var zookeeper = yield getTodaysZookeeper();
console.log('%s catches by %s', animal.name, zookeeper.name);
}
catchEscapedAnimal()
์ ajax๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ฐ์ ํด์ setTimeout
์ ์ด์ฉํด ์ฝ๋ฐฑ์ ํธ์ถํ๋ ํํ๋ก ์์ฑ๋์๋ค. getTodaysZookeeper()
๋ ์ผ๋ฐ์ ์ธ ์ ๋๋ ์ดํฐ ํจ์๋ก ์ฒซ ํธ์ถ์๋ loading์, ๋๋ฒ์งธ ํธ์ถ์์ ์ต์ข
๊ฐ์ ์ ์กํ๋ค. Zoo
๋ ์์์ ๋ณธ Cage
์ฒ๋ผ, ์ค๊ฐ์ yield๋ฅผ ์ฌ์ฉํ๋ค. ์ด ํจ์๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ compose
ํจ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
function compose(iter, value, next) {
var result = iter.next(value);
if(result.done) return next ? next(value) : value;
else if(typeof result.value == 'function') {
return result.value(function(err, data) {
if(err) throw err;
compose(iter, data);
});
} else if(typeof result.value.next == 'function') {
var _iter = iter;
next = function(result){
compose(_iter, result);
};
iter = result.value;
result = iter.next();
}
return compose(iter, result.value, next);
}
์ด compose
ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ์๋ฅผ ๋ค๋ฃฌ๋ค.
yield
๋ ๊ฐ์ด ํจ์์ผ ๋, ํธ์ถ ์ฒด์ธ์ ์ฐ๊ฒฐํ ์ ์๋๋กnext
ํจ์๋ฅผ ๋๊ฒจ์ค (๊ธฐ์กด callback ๋ฐฉ์)yield
๋ ๊ฐ์ด ์ดํฐ๋ ์ดํฐ์ผ ๋, ์ดํฐ๋ ์ดํฐ๊ฐ done์ ๋ฐํํ ๋๊น์ง ํธ์ถํ ํ ์ต์ข ๊ฐ์ ๋ฐํ- ๊ทธ ์ธ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ ๋, ํด๋น ๊ฐ์ ์ดํฐ๋ ์ดํฐ์ ๋ฃ๊ณ ๋ค์
compose
๋ฅผ ํธ์ถ - ์ดํฐ๋ ์ดํฐ๊ฐ ์ข
๋ฃ(
done == true
)๋์์ ๋,next
ํจ์๊ฐ ์๋ค๋ฉด ํด๋น ํจ์๋ก ํธ์ถ์ ์งํํ๊ณ ์์ผ๋ฉด ์ต์ข ๊ฐ์ ๋ฐํํ๊ณ ์ข ๋ฃ
์ด ํจ์๋ฅผ ์ด์ฉํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค. setTimeout()
์ ์ํด ์ค๊ฐ ์ง์ฐ์ด ์งํ๋๋ ๋ถ๋ถ๋ ํ์ธํ ์ ์๋ค.
compose(Zoo());
// Kuma catches by Edward
์ ๋๋ ์ดํฐ๋ฅผ ์ฝ๋ฃจํด์ผ๋ก, co
๋๋ฆ ์ ๋์ํ์ง๋ง ํ๋ฆ์ ๋ณด๊ธฐ ์ํด์ ๋ง๋ ํจ์๋ผ์ ํ์ ํ ๋ถ๋ถ์ด ๋ง๋ค. ์ด๋ฐ ๋ถ๋ถ์์ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด ๋ฐ๋ก co๋ค. co๋ ์ ๋๋ ์ดํฐ๋ฅผ ์ฝ๋ฃจํด์ฒ๋ผ ์ฌ์ฉํ ์ ์๋๋ก ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์์ ์์ฑํ๋ compose
ํจ์์ ๊ฐ์ ์ญํ ์ ํ๋ค.
var co = require('co');
co(Zoo());
// Kuma catches by Edward
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ด๋ถ์ ์ผ๋ก Promise ํจํด์ ์ฌ์ฉํ๊ณ ์์ด์ callback์ด๋ Promise๋ ์ ๋๋ ์ดํฐ๋ ๋ชจ๋ ์ ์ฒ๋ฆฌํ๋ค. ์ค์ ๋ก ์ ๋๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ํฐ ๋์์ด ๋๋ค.
Koa
Koa๋ ์์ ์ด์ผ๊ธฐํ co ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ฉํ๊ณ ์๋ HTTP ๋ฏธ๋ค์จ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ฒฝ๋์ ๊ฐ๋จํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๊ฒ์ ํน์ง์ผ๋ก ํ๋ค. ์ ๋๋ ์ดํฐ๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ด์ ์์ ๋ฐฐ์ด ๋ด์ฉ์ ์์ฝ๊ฒ ์ ์ฉํ ์ ์๋ค.
์ฝ๋๋ฅผ ์์ฑํ๊ธฐ์ ์์ ๊ฐ๋จํ๊ฒ koa๋ฅผ ์ค์นํ๋ค.
$ npm install --save koa
Hello World๋ฅผ ์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
var koa = require('koa');
var app = koa();
app.use(function* () {
this.body = {"message": "Hello World"};
});
app.listen(3000);
์ด์ http://localhost:3000์ ์ ์ํ๋ฉด ํด๋น json์ด ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์์ ์์ฑํ ์ฝ๋๋ ํฌํจํด๋ณด์.
var koa = require('koa');
var app = koa();
function catchEscapedAnimal() {
return function(done) {
setTimeout(function() {
done(null, {name: 'Kuma', type: 'Bear'});
}, 50);
};
}
function* getTodaysZookeeper() {
yield {status: 'loading'};
return {status: 'loaded', name: 'Edward'};
}
function* Zoo() {
var animal = yield catchEscapedAnimal();
var zookeeper = yield getTodaysZookeeper();
this.body = { message: animal.name + ' catches by ' + zookeeper.name };
}
app.use(Zoo);
app.listen(3000);
Koa์ ๋ชจ๋ ์ถ๊ฐ ๊ธฐ๋ฅ์ ๋ฏธ๋ค์จ์ด ๊ตฌ์กฐ๋ก ์ ๋๋ ์ดํฐ๋ฅผ ํตํด ์์ฑํ๊ฒ ๋๋ค. callback์ ๋ฌผ๋ก Promise ํจํด๋ ๋ ๊น๋ํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
์์ฒญ๊ณผ ์๋ต์ ๋ชจ๋ this
์ ์ฃผ์
๋์ ์ ๋ฌ๋๊ณ ํ๋ฆ์ ์ฒซ ์ธ์์ next
๋ฅผ ์ถ๊ฐํด ์ ์ดํ ์ ์๋ค. ์์ฒญ์ ๋ํ ์๋ต ๋ด์ฉ์ด ์์ผ๋ฉด ok
๋ฅผ ์ถ๊ฐํด๋ณด์.
app.use(function* (next) {
yield next;
if(this.body) {
this.body.ok = true;
} else {
this.body = { ok : false };
}
});
๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ํ ํฐ ๊ฒ์ฆ๋ ๊ฐ๋ฅํ๋ค.
app.use(function* (next) {
var requestToken = this.request.get("Authorization");
var accessToken = yield AccessTokensModel.findAccessTokenAsync(token);
if(accessToken) {
yield next;
} else {
this.body = { error: 'invalid_token' };
}
});
์ธ๋ถ์ ์ธ ๋ด์ฉ์ koa ์นํ์ด์ง์์ ๋ค๋ฃจ๊ณ ์๋ค. ๋จ์ํ๊ณ ๊ฐํธํ ๊ธฐ๋ฅ์ ์ํ๋ค๋ฉด ๊ผญ ์ดํด๋ณด์. ์ค์ ์ฌ์ฉํ๊ฒ ๋ ๋๋ koa-bodyparser, koa-router์ ๊ฐ์ ํจํค์ง๋ฅผ ๊ฐ์ด ์ฌ์ฉํ๊ฒ ๋๋ค. ํจํค์ง ๋ชฉ๋ก์ koa ์ํค์์ ํ์ธํ ์ ์๋ค.
์ ๋๋ ์ดํฐ๋ ์ถฉ๋ถํ ํธํ ๊ธฐ๋ฅ์ด์ง๋ง koa๋ ํ์ฌ await/async ๋ฌธ๋ฒ์ ์ง์ํ๊ธฐ ์ํ ๋ค์ ๋ฒ์ ๊ฐ๋ฐ์ด ์งํ๋๊ณ ์๋ค. ๋ ๊ฐ๋ ์ฑ๋ ๋๊ณ ๋ค๋ฅธ ์ธ์ด์์ ์ด๋ฏธ ๊ตฌํ๋์ด ๋๋ฆฌ ์ฌ์ฉ๋๊ณ ์๋ ๋ฌธ๋ฒ์ด๋ผ ๋ ๊ธฐ๋๋๋ค.