由于 Javascript 执行环境是“单线程”的,想要实现异步编程,就会用到回调函数。如果一些异步请求之间存在依赖,或者服务端使用 node 进行大量的 io 操作时就会出现回调函数嵌套的情况,代码就变成了横向发展,不利于阅读,维护起来麻烦。
//回调函数嵌套的情况
fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
})
})
解决回调函数嵌套问题可以使用:
ES7 中有了更加标准的解决方案,新增了 async/await 两个关键词。async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 promise 对象 resolve,并拿到结果。 相对于前面的3种方法,async 实现最简洁,符合语义,几乎没有语义不相关的代码。
async 是一个关键字,通常是放在函数声明之前,目的是将函数转换成异步函数,并返回 promise 对象。
// 将 a 函数转成异步函数,返回一个 promise 对象
async function a () { return 'a' }
// 上面的代码等同于
function a () { return Promise.resolve('a') }
// 调用
a().then(console.log) // 'a'
await 也是一个关键字,一般在使用了 async 的函数里定义,放在任何基于异步声明的函数之前,作用是暂停代码在该行上,直到 promise 完成,然后返回结果值。
async deleteTag ({ id }) {
const result = await Tag.remove({ _id: id })
if (!result) {
throw new Error('error')
}
return result
}
async createPost (post) {
const doc = new Post(post)
const result = await doc.save()
await Archive.updatePostId(result.id)
return result
}
deleteTag().then(result => { //... })
createPost().then(result => { //... })
deleteTag 函数通过 id 删除对应的标签,createPost 函数创建文章,成功创建后再更新存档数据。前面加上一个 async(表示一个异步函数),里面使用 await 语句(相当于暂停标志),就可以像写同步的代码一样解决回调函数嵌套的问题。
function sleep (timeout){
return new Promise(function(resolve){
setTimeout(function(){
resolve()
}, timeout)
})
}
;(async function(){
console.log(new Date())
await sleep(3000)
console.log(new Date())
})()
输出第一个 log 信息后,遇到 await 语句,执行 sleep 方法,然后会等待3秒才继续执行下面的代码。
由于它们属于 ES7 ,仅仅只是语法糖,想要在前端或者 node 服务端使用需要 babel 进行转译,或者使用 polyfill。