3. 콜백을 사용한 비동기 제어 흐름 패턴
3. 콜백을 사용한 비동기 제어 흐름 패턴
비동기 프로그래밍의 어려움
일반 JavaScript 사용
Async 라이브러리
깃헙주소
https://github.com/PacktPublishing/Node.js_Design_Patterns_Second_Edition_Code.git
Node.js는 비동기, 연속전달방식을 일반적으로 사용하는 플랫폼.
일반적인 문제, 실수중 하나 콜백지옥 -> 가독성 결여, 실수 발생
제어 흐름 라이브러리를 사용하여 콜백패턴을 단순화하고 깔끔하게 만들기 목표
3.1 비동기 프로그래밍의 어려움
클로저와 인플레이스정의(in place definitions)의 활용이 관건
KISS(Keep in simple, stupid) 간단함을 유지해 멍청아ㅋㅋ
클로저 -> 비동기보다는 규칙문제가 더중요한 경우
3.1.1 간단한 웹스파이더 만들기
간단 예제
npm 라이브러리 request, mkdirp 사용
// 깃헙 : https://github.com/PacktPublishing/Node.js_Design_Patterns_Second_Edition_Code/blob/master/Chapter03/01_web_spider/package.json
{
"name": "crawler",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"mkdirp": "^0.5.1",
"request": "^2.67.0",
"slug": "^0.9.1"
}
}콜백 지옥, 죽음의 피라미드 -> 가독성이 떨어짐
비동기 CPS(직접전달방식), 클로저 잘못 사용? -> 큰 재앙
클로저가 참조하는 컨텍스트가 가비지 수집시 유지된다는 사실... -> 잘못사용하면 메모리 누수
3.2 일반 JavaScript의 사용
비동기 코드 작성시 또다른 주의 사항
특정 패턴과 기법을 사용해야하는 상황? -> 일반 JS를 사용할 때 특히
음... ㅇㅋ 일단 패스
여기서는 일반 JS를 사용하여 일반적인 제어흐름 패턴 구현
3.2.1 콜백 규치
콜백을 정의할때, 함부로 클로저를 사용하지 않는 것
콜백 지옥 문제 해결 -> 간단한 일반 상식이면 충분.
중첩 수준을 낮게 유지하는 팁
가능한 빨리 종료. if문 수 줄이기. ex return, break, continue 문 활용콜백을 위해 명명된 함수(익명말고)를 생성하여, 클로저 바깥에 배치하며, 중간 결과를 인자로 전달.
명명함수 -> 스택추적에서 잘보임코드 모듈화. 코드를 작고 재사용 가능하게 함수 구현
3.2.2 콜백 규칙 적용
if~else문 제거 -> 오류 검사 패턴 재구성 (상태패턴에서 활용 되기도 함)
3.2.3 순차실행
순서가 중요한 경우 순서에 따라 IN->OUT -> IN->OUT 으로 결과값이 입력값으로 가는 파이프형태 순차실행 적용 : 직접방식의 블로킹 API에서는 간단, 비동기 CRS 사용하여 구현 -> 콜백지옥의 원인
잘알려진 순차실행
순차반복
비동기적으로 배열을 반복 실행하게끔 구현되어 있음.
```javascript
// index.js
"use strict";
const request = require('request'); const fs = require('fs'); const mkdirp = require('mkdirp'); const path = require('path'); const utilities = require('./utilities3');
function spiderLinks(currentUrl, body, nesting, callback) { if(nesting === 0) { return process.nextTick(callback); }
let links = utilities.getPageLinks(currentUrl, body); //[1] 내부 링크 다가져옴
function iterate(index) { //[2] 반복 패턴, 페이지 링크 수와 인덱스가 같아질때까지 반복 if(index === links.length) { return callback(); }
} iterate(0); //[4] 최초 0부터 시작, 재귀시작 }
function saveFile(filename, contents, callback) { mkdirp(path.dirname(filename), err => { if(err) { return callback(err); } fs.writeFile(filename, contents, callback); }); }
function download(url, filename, callback) { console.log(Downloading ${url}); request(url, (err, response, body) => { if(err) { return callback(err); } saveFile(filename, body, err => { if(err) { return callback(err); } console.log(Downloaded and saved: ${url}); callback(null, body); }); }); }
function spider(url, nesting, callback) { const filename = utilities.urlToFilename(url); fs.readFile(filename, 'utf8', function(err, body) { if(err) { if(err.code !== 'ENOENT') { return callback(err); }
}); }
spider(process.argv[2], 1, (err) => { if(err) { console.log(err); process.exit(); } else { console.log('Download complete'); } });
// utilities.js "use strict";
const urlParse = require('url').parse; const urlResolve = require('url').resolve; const slug = require('slug'); const path = require('path'); const cheerio = require('cheerio');
module.exports.urlToFilename = function urlToFilename(url) { const parsedUrl = urlParse(url); const urlPath = parsedUrl.path.split('/') .filter(function(component) { return component !== ''; }) .map(function(component) { return slug(component); }) .join('/'); let filename = path.join(parsedUrl.hostname, urlPath); if(!path.extname(filename).match(/htm/)) { filename += '.html'; } return filename; };
module.exports.getLinkUrl = function getLinkUrl(currentUrl, element) { const link = urlResolve(currentUrl, element.attribs.href || ""); const parsedLink = urlParse(link); const currentParsedUrl = urlParse(currentUrl); if(parsedLink.hostname !== currentParsedUrl.hostname || !parsedLink.pathname) { return null; } return link; };
module.exports.getPageLinks = function getPageLinks(currentUrl, body) { return [].slice.call(cheerio.load(body)('a')) .map(function(element) { return module.exports.getLinkUrl(currentUrl, element); }) .filter(function(element) { return !!element; }); };
3.2.4 병렬실행
단일스레드 인데 병렬 가능? -> 논블로킹 때문에 그렇게 보이는 것 -> 논블로킹 위에서 실행되고 있기 때문 가능 -> 정확히는 논블로킹 API위에서 실행되고, 이벤트 루프에 의해 인터리브 된다는 것을 의미
동기(블로킹)작업 -> 비동기로 처리, setTimeout(), setImmediate()로 지연 시키지 않는 한 동시에 실행 안됨 (9장에서 다룸)
웹스파이더 3버전 병렬성을 띄어 성능을 향상 시킴
패턴 일반화
```javascript
const tasks = [];
let completed = 0;
tasks.forEach(task => {
if(++completed === tasks.length){
finish();
}
})
function finish() { // work }
3.3 Async 라이브러리
Last updated
Was this helpful?