Node. js 리 셋 블랙홀 전 해: Async, Promise, Generator
30414 단어 NodeJS
우 리 는 항상 이 문 제 를 '블랙홀 반전' 또는 '피라미드 반전' 이 라 고 부른다.
doAsync1(function () {
doAsync2(function () {
doAsync3(function () {
doAsync4(function () {
})
})
})
블랙홀 을 되 돌 리 는 것 은 주관적 인 명칭 으로, 마치 너무 많은 코드 를 끼 워 넣 은 것 처럼, 때로는 아무런 문제 도 없다.호출 순 서 를 제어 하기 위해 비동기 코드 가 매우 복잡 해 졌 는데 이것 이 바로 블랙홀 이다.블랙홀 이 얼마나 깊 은 지 가늠 하기에 적합 한 문제 가 있다.
만약 doAsync 2 가 doAsync 1 이전에 발생 한다 면, 당신 은 얼마나 많은 재 구성 고통 을 참아 야 합 니까?
목 표 는 내장 층 수 를 줄 이 는 것 이 아니 라 모듈 화 (테스트 가능) 코드 를 작성 하여 이해 하고 수정 하 는 것 입 니 다.
본 논문 에서 우 리 는 하나의 모듈 을 만들어 서 일련의 도구 와 라 이브 러 리 를 사용 하여 흐름 제어 가 어떻게 작 동 하 는 지 보 여 줘 야 한다.그리고 새로운 Node 가 새로운 잠재력 있 는 해결 방안 을 가 져 왔 으 니 끝까지 알 아 보 겠 습 니 다.
문제.
만약 우리 가 어떤 디 렉 터 리 에서 가장 큰 파일 을 찾 아야 한다 고 가정 한다.
var findLargest = require('./findLargest')
findLargest('./path/to/dir', function (er, filename) {
if (er) return console.error(er)
console.log('largest file was:', filename)
})
우 리 는 단계별 로 이 문 제 를 해결한다.
모든 부분 에 오류 가 발생 하면 리 셋 함 수 를 호출 하여 오 류 를 전달 합 니 다.우 리 는 단지 리 셋 함 수 를 한 번 호출 할 뿐이다.
내장 코드
첫 번 째 해결 방안 은 포 함 된 것 으로 무 서운 것 이 없어 보이 고 논 리 는 알 아 볼 수 있다.
var fs = require('fs')
var path = require('path')
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) { [1]
if (er) return cb(er)
var counter = files.length
var errored = false
var stats = []
files.forEach(function (file, index) {
fs.stat(path.join(dir,file), function (er, stat) { [2]
if (errored) return
if (er) {
errored = true
return cb(er)
}
stats[index] = stat [3]
if (--counter == 0) { [4]
var largest = stats
.filter(function (stat) { return stat.isFile() }) [5]
.reduce(function (prev, next) { [6]
if (prev.size > next.size) return prev
return next
})
cb(null, files[stats.indexOf(largest)]) [7]
}
})
})
})
}
이 해결 방안 은 그런대로 괜 찮 지만, 그것 은 몇 가지 기 교 를 운용 하여 병행 처 리 를 관리 하고, 여러 번 호출 함 수 를 피한다.우 리 는 그 후에 다시 이 문제 들 을 처리 할 것 이다. 우선 이 코드 를 더 작은 모듈 로 나 누 자.
모듈 화
코드 를 끼 워 넣 는 방안 은 세 개의 모듈 단위 로 나 눌 수 있다.
fs.readdir
우 리 는 그것 을 위해 함 수 를 새로 쓰 지 않 을 것 이다.단, 우 리 는 일련의 파일 경 로 를 정 하고 이 파일 들 의 stat 를 되 돌려 주 는 함 수 를 쓸 수 있 습 니 다.
function getStats (paths, cb) {
var counter = paths.length
var errored = false
var stats = []
paths.forEach(function (path, index) {
fs.stat(path, function (er, stat) {
if (errored) return
if (er) {
errored = true
return cb(er)
}
stats[index] = stat
if (--counter == 0) cb(null, stats)
})
})
}
다음은 stats 와 files 를 비교 하여 최대 파일 의 파일 이름 을 되 돌려 주 는 처리 함수 가 필요 합 니 다.
function getLargestFile (files, stats) {
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
return files[stats.indexOf(largest)]
}
한데 묶 기:
var fs = require('fs')
var path = require('path')
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) {
if (er) return cb(er)
var paths = files.map(function (file) { [1]
return path.join(dir,file)
})
getStats(paths, function (er, stats) {
if (er) return cb(er)
var largestFile = getLargestFile(files, stats)
cb(null, largestFile)
})
})
}
모듈 화 된 해결 방안 은 코드 재 활용 과 테스트 를 더욱 쉽게 한다.핵심 적 인 export 방법 도 이해 하기 쉽다.그러나, 우 리 는 수 동 으로 병행 하 는 stat 작업 을 관리 합 니 다.흐름 제어 라 이브 러 리 를 사용 해 보 겠 습 니 다. 우리 가 무엇 을 할 수 있 는 지 볼 까요?
Async async
모듈 은 노드 의 핵심 정신과 유사 하 게 유행 하고 있다.async 를 사용 하여 코드 를 재 구성 하 는 방법 을 알 아 보 겠 습 니 다.
var fs = require('fs')
var async = require('async')
var path = require('path')
module.exports = function (dir, cb) {
async.waterfall([ [1]
function (next) {
fs.readdir(dir, next)
},
function (files, next) {
var paths =
files.map(function (file) { return path.join(dir,file) })
async.map(paths, fs.stat, function (er, stats) { [2]
next(er, files, stats)
})
},
function (files, stats, next) {
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
next(null, files[stats.indexOf(largest)])
}
], cb) [3]
}
Promise Promise
오류 처리 와 함수 식 프로 그래 밍 을 제공 합 니 다.
。우 리 는 어떻게 Promise 를 사용 하여 이 문 제 를 해결 합 니까?Q 를 사용 하 래 요.
모듈 시험 해 보기 (물론 다른 Promise 라 이브 러 리 도 사용 할 수 있 습 니 다):
var fs = require('fs')
var path = require('path')
var Q = require('q')
var fs_readdir = Q.denodeify(fs.readdir) [1]
var fs_stat = Q.denodeify(fs.stat)
module.exports = function (dir) {
return fs_readdir(dir)
.then(function (files) {
var promises = files.map(function (file) {
return fs_stat(path.join(dir,file))
})
return Q.all(promises).then(function (stats) { [2]
return [files, stats] [3]
})
})
.then(function (data) { [4] var files = data[0]
var stats = data[1] var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
return files[stats.indexOf(largest)]
})
}
이전 예 와 달리 promise 체인 에서 던 진 오 류 는 처 리 됩 니 다.그리고 드 러 난 API 도 Promise 화 된 것 입 니 다.
var findLargest = require('./findLargest')
findLargest('./path/to/dir')
.then(function (er, filename) {
console.log('largest file was:', filename)
})
.catch(console.error)
위 에 이렇게 디자인 되 어 있 음 에 도 불구 하고 promise 화 된 인 터 페 이 스 를 노출 할 필 요 는 없다.많은 promise 라 이브 러 리 도 node 스타일 의 인 터 페 이 스 를 노출 하 는 방법 을 제공 했다.Q 에서 nodeify 를 사용 할 수 있 습 니 다.
함수 로 실현 하 다.
Promise 를 깊이 소개 하지 않 겠 습 니 다. 더 알 고 싶다 면 이 글 을 읽 으 세 요.
Generator
본 고 는 처음부터 말 했 듯 이 이 분야 에서 신기 술 이 왔 는데 Node 0.11.2 와 이상 버 전에 서 이미 사용 할 수 있 습 니 다.
Generator!
Generator 는 자 바스 크 립 트 를 위 한 경량급 협정 이다.이 는 yield 키 워드 를 통 해 함수 가 일시 정지 되 거나 Generator 함 수 를 계속 실행 하 는 것 을 제어 할 수 있 습 니 다. 특별한 문법 function * () 이 있 습 니 다. 이러한 초능력 을 통 해 우 리 는 비동기 작업 을 일시 정지 하거나 계속 수행 할 수 있 습 니 다. promise 나 thunks 와 같은 구 조 를 사용 하여 동기 화 된 코드 를 쓸 수 있 습 니 다.
Thunk 함 수 는 이러한 함수 입 니 다. 반전 을 되 돌려 자신 을 호출 합 니 다.리 턴 함수 와 전형 적 인 node 리 턴 함수 가 일치 하 는 매개 변 수 를 가지 고 있 습 니 다 (예 를 들 어 error 는 첫 번 째 매개 변수 입 니 다).여 기 를 읽 으 면 더 많이 알 수 있다.
Generator 를 어떻게 사용 하여 비동기 적 인 통 제 를 하 는 지 예 를 들 어 보 겠 습 니 다. TJ Holowaychuk 의 라 이브 러 리 코 입 니 다.다음은 우리 가 가장 큰 파일 을 찾 는 프로그램 입 니 다.
var co = require('co')
var thunkify = require('thunkify')
var fs = require('fs')
var path = require('path')
var readdir = thunkify(fs.readdir) [1]
var stat = thunkify(fs.stat)
module.exports = co(function* (dir) { [2] var files = yield readdir(dir) [3]
var stats = yield files.map(function (file) { [4]
return stat(path.join(dir,file))
})
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
return files[stats.indexOf(largest)] [5]
})
try {
var files = yield readdir(dir)
} catch (er) {
console.error('something happened whilst reading the directory')
}
Co 는 배열, 대상, 내장 Generator, Promise 등 도 우아 하 게 지원 할 수 있다.
다른 Generator 모듈 도 생 겨 났 다.Q 모듈 에서 우아 한 Q. async 를 신 고 했 습 니 다.
방법, 행 위 는 co 사용 Generator 와 일치 합 니 다.
총결산
본 논문 에서 우 리 는 블랙홀 을 되 돌 리 는 여러 가지 다른 방안 을 연구 했다.사실은 프로그램의 절차 통제 다.저 는 개인 적 으로 Generator 의 방안 에 관심 이 많 습 니 다.나 는 koa 와 같은 새로운 틀 이 무엇 을 가 져 올 수 있 는 지 매우 궁금 하 다.
본 논문 에서 사용 한 코드 예제 와 다른 Generator 예 를 원 하 십 니까?여기 Github repo 가 있어 요. 만족 할 수 있어 요!본 고 는 StrongLoop | Managing Node. js Callback Hell with Promises, Generators and Other Approaches 에서 시작 되 었 습 니 다.
。