在ES7的中,我们可以使用async & await进行编写异步函数,使用这种写法我们的异步函数看起来就跟同步代码一样。
在之前的版本(ES6),可以使用Promise写法,来简化我们异步编程的流程,同时也避免了回调地狱。
回调地狱
回调地狱是语义化产生的一个术语,它的释义可以用下面这种情况进行阐述:
function AsyncTask() {
asyncFuncA(function(err, resultA){
if(err) return cb(err);
asyncFuncB(function(err, resultB){
if(err) return cb(err);
asyncFuncC(function(err, resultC){
if(err) return cb(err);
// And so it goes....
});
});
});
}
上例代码中, 不断的回调,使得代码维护和管理控制流程变得十分的困难。 我们不妨考虑下这种情况,假如某个if语句需要执行其他的方法,而回调函数FunctionA的结果为foo。
使用Promise优化
ES6和Promise的出现,使得我们可以简化之前"回调地狱"般的代码如下:
function asyncTask(cb) {
asyncFuncA.then(AsyncFuncB)
.then(AsyncFuncC)
.then(AsyncFuncD)
.then(data => cb(null, data)
.catch(err => cb(err));
}
这样编写是不是看起来舒服多了?
但是在实际的业务场景中,异步流的处理可能会更加复杂一些。举例来说,
假如在你的一个(node.js)服务器中,你可能想要:
- 将一个数据1保存到数据库中 (步骤1)
- 根据保存的数据1查找另外一个数据2 (步骤2)
- 如果查找到了数据2,执行其他的一些异步任务 (其他任务)
- 等到所有的任务全部执行完成之后,你可能需要使用你在(步骤1)中得到的结果用来反馈给用户。
- 假如在执行任务的过程中发生了错误,你得要告诉用户在哪个步骤发生了错误。
在使用了Promise语法后,这样当然看起来更加的简洁了,但是,在我看来仍然有一点混乱。
ES7 Async/await
您需要使用转译器才能使用Async/Await,您可以使用babel插件或Typescript来添加所需的工具。
此时, 若使用 async/await, 你会发现代码写起来舒服多了. 它允许我们像下面一样编写代码:
async function asyncTask(cb) {
const user = await UserModel.findById(1);
if(!user) return cb('No user found');
const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
if(user.notificationsEnabled) {
await NotificationService.sendNotification(user.id, 'Task Created');
}
if(savedTask.assignedUser.id !== user.id) {
await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
}
cb(null, savedTask);
}
上面的代码看起来可读性增强了不少, 但是如何处理错误报错呢?
异常处理
在执行异步任务使用Promise的时候可能会发生一些错误类似数据库连接出错,数据库模型验证错误等情况。
当一个异步函数正在等待Promise返回值的时候,当Promise方法报了错误的时候,它会抛出异常,这个异常可以在catch方法里面捕获到。
在使用Async/Await时,我们通常使用try/catch语句进行异常捕获。
try{
//do something
}
catch{
// deal err
}
我没有编写强类型语言的背景,因此增加额外的try/catch
语句, 对我来说增加了额外的代码,这在我看来非常的冗余不干净。
我相信这可能是个人喜好的原因,但这是我对此的看法。
所以之前的代码看起来像这样:
async function asyncTask(cb) {
try {
const user = await UserModel.findById(1);
if(!user) return cb('No user found');
} catch(e) {
return cb('Unexpected error occurred');
}
try {
const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
} catch(e) {
return cb('Error occurred while saving task');
}
if(user.notificationsEnabled) {
try {
await NotificationService.sendNotification(user.id, 'Task Created');
} catch(e) {
return cb('Error while sending notification');
}
}
if(savedTask.assignedUser.id !== user.id) {
try {
await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
} catch(e) {
return cb('Error while sending notification');
}
}
cb(null, savedTask);
}
另想办法
最近我一直在使用go-lang
进行编码,并且非常喜欢他们的解决方案,它的代码看起来像这样:
data, err := db.Query("SELECT ...")
if err != nil { return err }
我认为它比使用try/catch
语句块更加简洁,并且代码量更少,这使得它可读和可维护更好。
但是使用Await的话,如果没有为其提供try-catch处理异常的话,当程序发生错误的时候,它会默默的退出(你看到不抛出的异常)。假如你没有提供catch语句来捕捉错误的话,你将无法控制它。
当我和Tomer Barnea(我的好朋友)坐在一起并试图找到一个更简洁的解决方案时,我们得到了下一个使用方法: 请记住: Await在等待一个Promise返回值
async & await 异常捕捉工具函数
有了这些知识,我们就可以制作一个小的通用函数来帮助我们捕捉这些错误。
// to.js
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
这个通用函数接收一个Promise,然后将处理成功的返回值以数组的形式作为附加值返回,并且在catch方法中接收到捕捉到的第一个错误。
import to from './to.js';
async function asyncTask(cb) {
let err, user, savedTask;
[err, user] = await to(UserModel.findById(1));
if(!user) return cb('No user found');
[err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
if(err) return cb('Error occurred while saving task');
if(user.notificationsEnabled) {
const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
if(err) return cb('Error while sending notification');
}
cb(null, savedTask);
}
上面的例子只是一个使用该解决方案的简单用例,你可以在io.js中添加拦截方法(类似调试的断点),该方法将接收原始错误对象,打印日志或者进行其他任何你想要进行的操作,然后再返回操作后的对象。
我们为这个库创建了一个简单的NPM包(Github Repo),您可以使用以下方法进行安装:
npm i await-to-js
这篇文章只是寻找Async/Await功能的一种不同方式,完全基于个人意见。 您可以使用Promise,仅使用try-catch和许多其他解决方案来实现类似的结果。 只要你喜欢并且它适用。
引发思考
async/await 结合promise使用就好了, 即异步流程控制的最后加上 promise的catch.
async function task(){
return await req();
}
task().catch(e => console.error(e))
结合async/await 结合Promise.all使用时, 如何捕获异常&处理?
async function hello(flag){
return new Promise((resolve, reject) => {
if(flag) setTimeout(() => resolve('hello'), 100);
else reject('hello-error');
})
}
async function demo(flag){
return new Promise((resolve, reject) => {
if(flag) setTimeout(() => resolve('demo'), 100);
else reject('demo-error');
})
}
async function main(){
let res = await hello(1).catch(e => console.error(e));
console.log('res => ', res);
let result = await Promise.all([hello(1), demo(1)]);
// let result = await Promise.all([hello(1), demo(0)]).catch(e => console.error('error => ', e));
console.log('result => ', result);
}
main()