Callback 函数
当IO操作完成就回调函数,简单又熟悉
fs.readFile('/path/to/file', (err, data) => {
if (err) {
return console.error('读取文件错误')
}
console.log(data)
})
回调陷阱
- 处理错误后忘记
return
fs.readFile('/path/to/file', (err, data) => {
if (err) { // 文件不存在
/*return*/
console.error('文件没有找到')
}
console.log('找到文件后做某些操作!')
})
- 使用不同的参数多次调用回调函数
function doThingAsync(cb) {
fs.readFile('/path/to/file', (err, data) => {
if (error) {
cb(err)
}
if (data.toString() === 'special') {
cb(null, '数据已找到')
}
cb(null, '没有数据special')
})
}
doThingAsync(function (err, data) {
console.log('我会执行两次!')
})
解决回调陷阱
- 在任何时候回调之后就return
if (err) {
return callback(err)
}
return callback(null, data)
- 保证执行一次
doMyAsyncThing(_.once((err, data) => {
// 这个方法只可以执行一次,
})
_.once由lodash提供
回调提示
提高可读性,避免嵌套减少名称冲突
fetchUser(user_id, (err, user) => {
fetchPosts(user_id, (err2, posts) => {
fetchSomethingElse(user_id, (err3, other_stuff) => {
if (err) { /* OOPS!应该使用 err3! */ }
})
})
})
正确姿势
function onPostsRetrieved(err, posts) { /* ... 获取数据 */ }
function onUserRetrieved(err, user) {
fetchPosts(user.user_id, onPostsRetrieved)
}
fetchUser(user_id, onUserRetrieved)
Promises
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
- Promise.resolve
创建指定resolved状态值的Promise
const foo = Promise.resolve('last')
foo.then(x => {
console.log('接着执行 ' + x)
})
foo.then(x => {
console.log('再接下来执行 ' + x)
})
console.log('最先被执行')
- Promise.reject
创建指定rejectd状态值的Promise
const foo = Promise.reject('rejection!')
foo.then(x => {
console.log('这里不会被执行,因为Promise进入rejected状态')
})
foo.catch(x => {
console.log(x)
})
foo.catch(x => {
console.log('Another ' + x)
})
- new Promise()
提供resolve和reject回调
new Promise((resolve, reject) => {
fs.readFile('/path/to/file', (err, data) => {
if (err) {
return reject(err)
}
return resolve(data)
})
})
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})
- 组合使用Promises
const promise1 = Promise.resolve(4);
const promise2 = Promise.resolve(5);
promise1.then(a => {
/*return promise2.then(b => {
return b+a;
})*/
return {name:'test'};
}).then(c => {
console.info(c); //{ name: 'test' }
});
从then返回的任何值都会被添加到promise链
- Promise chain
promiseA
.then(() => throw new Error('Error')) // 假设 promiseB 抛出一个错误.
.catch(err => console.log(err)) // C1
.then(() => promiseC)
.then(() => promiseD)
.catch(err => console.log(err)) // C2
promiseA
.then(() => throw new Error('Error'))
.catch(err => throw err) // C1
.then(() => promiseC)
.then(() => promiseD)
.catch(err => console.log(err)) // C2
- Promise.all
Promise.all(promise集合) 方法返回一个promise,该promise会等promise集合内的所有promise都被resolve后被resolve,或以第一个promise被reject的原因而reject
const userService = require('./services/user')
const users_promise = Promise.all([
userService.get(123),
userService.get(555)
])
users_promise.then((users) => {
let [user123,user555] = users;
console.log('全部用户服务已经执行成功!')
})
.catch((error) => {
console.log('其中一个用户服务调用失败!')
})
对于执行顺序不重要的任务可以使用
- Promise.race
Promise.race(promise集合)方法返回一个promise,这个promise在promise集合中的任意一个promise被解决或拒绝后,立刻以相同的解决值被解决或以相同的拒绝原因被拒绝。
const timeout = new Promise((resolve, reject) => {
setTimeout(reject, 10000)
})
Promise.race([ timeout, userService.get(123) ])
.then(user => {
console.log(user)
})
.catch(error => {
// 错误有可能来自timeout或者userService.get
console.log(error)
})
Promise的陷阱
- 忘记处理错误
Promise.reject('original')
.catch(err => {
return Promise.resolve('default')
})
.then(res => {
console.log(res) // res -> 来自catch的返回值
})
Promise.reject('original')
.then(res => {
console.log(res) // 不会被执行
})
.catch(err => {
return Promise.resolve('default')
})
- 忘记返回Promise
Promise.resolve('/path/to/file')
.then(filename => {
/*return*/ new Promise((resolve, reject) => {
fs.readFile(filename, (err, data) => {
if(err) { return reject(err) }
return resolve(data)
})
})
})
.then(res => {
console.log('result: ' + res)
})
- 对event loop事件轮询的冲击
const userService = require('./services/user')
// 如果user基数非常大
const users_to_fetch = [1, 2, 5, 19, ... 9999]
const get_users_promises = _.map(users_to_fetch, (user_id) => {
return userService.get(user_id)
})
// 注意: 全部的Promise任务已经开始运行!
Promise.all(get_users_promises)
- 解决event loop事件轮询的冲击
使用bluebird map方法
function timeoutPromise(timeout) {
return new Promise(resolve => {
setTimeout(resolve, timeout)
})
}
const things_to_do = new Array(100)
// Promise.map 来自 bluebird (没有可用原生接口实现) -- 大概在两秒内完成
Promise.map(things_to_do, () => timeoutPromise(200), { concurrency: 10 })
.then(() => console.log('Batch complete!'))
Promise.all(things_to_do.map(() => timeoutPromise(200)))
.then(() => console.log('Flood complete!'))
Generator
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
- 基本
function *oneGenerator() {
const a = yield 1
const b = yield 1
console.log('result 1: ' + a)
console.log('result 2: ' + b)
}
// 创建生成器实例
const generator = oneGenerator()
//第一次输入将会被忽略
console.log(generator.next('a').value)
console.log(generator.next('b').value)
console.log(generator.next('c').value)
- Generator不会阻塞事件轮询
生成无限序列或推迟计算
function *myGenerator() {
let i = 1
while (true) {
yield i++
}
}
const generator = myGenerator()
console.log(generator.next().value)
console.log(generator.next().value)
console.log(generator.next().value)
- 组合使用生成器
function *oneGenerator() {
yield 1
yield* twoGenerator()
yield 1
}
function *twoGenerator() {
yield 2
}
const generator = oneGenerator()
console.log(generator.next().value) // 运行generator的第一个yield.
console.log(generator.next().value) // 现在执行twoGenerator!
console.log(generator.next().value) // 返回到oneGenerator
- Generator辅助异步的执行 通过组合Generator和Promise执行异步操作
Promise.coroutine(function* () {
yield Promise.delay(500)
console.log('after first delay')
yield Promise.delay(1000)
console.log('延迟几秒后')
})()
Promise.coroutine由bluebired提供,此外还有tj的CO库
- (Generator + Promises) vs Promises
Promise.coroutine(function* () {
try {
const resultA = yield Promise.resolve('foobar')
const resultB = yield Promise.resolve('baz')
const resultC = yield Promise.resolve('super')
console.log('GENERATORS: ' + resultA + resultB + resultC)
} catch (error) { console.log(error) }
})()
Promise.resolve('foobar')
.then(resultA => {
return Promise.resolve('baz')
.then(resultB => {
return Promise.resolve('super')
.then(resultC => console.log('NESTED: ' + resultA + resultB + resultC))
})
})
.catch(error => console.log(error))
- Generator与Promise并行异步任务
Promise.coroutine(function *() {
const results = yield Promise.all([
Promise.resolve(1),
Promise.resolve(2)
])
console.log('array', results[0], results[1])
const [a, b] = yield Promise.all([
Promise.resolve(1),
Promise.resolve(2)
])
console.log('destructured', a, b)
})()
生成器默认支持Node.js版本4+
async/await
- 从Generator到async/await
Promise.coroutine(function* () {
const [a, b] = yield Promise.all([
Promise.resolve(1),
Promise.resolve(2)
])
console.log(a, b)
})()
async function () {
const [a, b] = await Promise.all([
Promise.resolve(1),
Promise.resolve(2)
])
console.log(a, b)
}
- 调试异步JavaScript
为作为参数的函数命名
function doMyThing() {
setTimeout(() => {
console.log('NOT NAMED: ' + new Error().stack)
}, 50)
}
doMyThing()
正确的方式
function doMyThing() {
setTimeout(function fooIsMyName() {
console.log(' NAMED: ' + new Error().stack)
}, 50)
}
doMyThing()
- chrome中开启async追踪
Thank
由 MyFreax整理