用async/await改造Node.js(Express)网站
Mike的读书季IP属地: 北京 2018.11.02 00:13:00字数 582阅读 3,1151.回调的嵌套陷阱
在Node.js中,使用回调的方式进行异步操作,我们以读取文件内容为例:
const fs = require('fs');
// 定义一个以回调的方式获取文件的函数
function asyncReadFile(path, callback) {
fs.readFile(path, 'utf-8', function(err, data) {
callback(err,data)
})
}
// 调用:
router.get('/', async function(req, res, next) {
asyncReadFile("./package.json", (err, data) {
console.log(data)
// 渲染页面
res.render('index', { title: 'Express' });
})
})
从上面的调用不难推测出,以回调的方式来实现异步,嵌套将会是开发者的噩梦:
router.get('/', async function(req, res, next) {
asyncReadFile("./package.json", (err, data1) {
console.log(data1)
asyncReadFile("./app.js", (err, data2) {
console.log(data2)
asyncReadFile("./other.js", (err, data3) {
console.log(data3)
// ...
// 渲染页面
res.render('index', { title: 'Express' });
})
})
})
})
在业务查询比较多、需要同时触发的任务中,回调会严重影响排版及阅读,尤其对接手人(甚至开发者自己)的阅读和理解造成了很大的困难。
2.使用async/await
接下来,我将改造上面的 asyncReadFile()
函数,以将返回值其构造为 Promise
对象。
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
resolve(data)
})
})
}
router.get('/', async function(req, res, next) {
var file = await asyncReadFile("./package.json")
console.log(file)
// 渲染
res.render('index', { title: 'Express' });
});
这里需要注意的是,async
和 await
是成对出现的。即你要在哪个函数使用 await
的方式,就应当对这个函数进行 async
声明。
事实上,这个声明是一种语法糖,即在不改变语法的基础上,让代码的可读性更好、更不容易出错。async就是Generator函数的语法糖。
Generator函数的用法是这样的:
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
};
对应的async用法则是:
var gen = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
};
即将 *
和 yield
分别用 async
和 await
替换了。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
3.处理reject()
在函数中添加 reject()
的调用,并在Promise对象后面追加一个 .catch()
的处理
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
if(err) {
reject(err)
}
resolve(data)
})
})
// This must be called in case of node-process terminated by reject()
.catch((err)=>{
return err
})
}
router.get('/', async function(req, res, next) {
var file = await asyncReadFile("./package.json")
console.log(file)
// 渲染
res.render('index', { title: 'Express' });
});
或者在调用的时候 catch 错误
var fs = require('fs')
var asyncReadFile = function(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf-8', function(err, data) {
if(err) {
reject(err)
}
resolve(data)
})
})
}
router.get('/', async function(req, res, next) {
try {
var file = await asyncReadFile("./package.json")
console.log(file)
}catch (e) {
console.error(e);
}
// 渲染
res.render('index', { title: 'Express' });
});
此外,需要注意的是,一旦 reject()
执行,后面的代码就立即停止了。
async function func() {
await Promise.reject(err);
await Promise.resolve(); // 不会执行
}
4.多个await函数的并发
如果由多个await函数要一起执行,且没有先后关系,可以让它们同时执行,原型为:
var [f1, f2, ...] = await Promise.all([func1, func2, ...])
var [f1, f2] = await Promise.all([
asyncReadFile("./app.js"),
asyncReadFile("./package.json")
])
结果集中 f1
即 asyncReadFile("./app.js")
的返回值, f2
即 asyncReadFile("./package.js")
的返回值。
参考资料:
《ECMAScript6笔记:异步操作和Async函数》