ES7 Async / await允许我们作为开发人员编写看起来是同步的异步JS代码。在当前的JS版本中,我们将Promises引入,这使我们能够简化异步流程并避免回叫。
回调地狱是一个术语,用于描述JS中的以下情况:
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语句,如果callbackA的某些结果等于'foo',则该语句需要执行其他Async方法。
营救承诺
有了promise和ES6,我们可以将以前的代码梦simplify简化为以下内容:
function asyncTask(cb) {
asyncFuncA.then(AsyncFuncB)
.then(AsyncFuncC)
.then(AsyncFuncD)
.then(data => cb(null, data)
.catch(err => cb(err));
}
看起来好多了,您不觉得吗?
但是在现实世界中,异步流程可能会变得有些复杂,例如,在服务器模型(nodejs)中,您可能希望将一个实体保存到数据库,然后根据保存的值查找其他一些实体(如果该值存在) ,执行其他一些异步任务,在所有任务完成后,您可能希望使用步骤1中创建的对象对用户进行响应。并且,如果在其中一个步骤中发生错误,则要通知用户确切的错误。
有了当然的承诺,与普通的回调相比,它看起来要干净得多,但是,仍然会有点混乱恕我直言。
ES7异步/等待
注意:您需要使用转译器才能享受异步/等待,可以将babel或打字稿用于所需的polyfills。
那是我发现异步等待真正有用的地方,它允许您编写如下代码:
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期间可能会发生某些情况(数据库连接错误,数据库模型验证错误等)。
由于异步函数正在等待Promises,因此当Promise遇到错误时,它将引发一个异常,该异常将在Promise的catch方法内捕获。
在异步/等待功能中,通常使用try / catch块来捕获此类错误。
我不是来自打字语言的背景,因此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的一个我的好朋友坐在一起,试图寻找更清洁的解决方案时,我们使用了以下方法:
还记得等待正在等待解决的诺言吗?
有了这些知识,我们可以制作小的实用程序函数来帮助我们捕获这些错误:
// to.js
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
效用函数接收一个promise,然后将对成功的响应解析为一个以返回数据为第二项的数组。并从第一个捕获到的错误。
然后,我们可以使异步代码如下所示:
import to from './to.js';
async function asyncTask() {
let err, user, savedTask;
[err, user] = await to(UserModel.findById(1));
if(!user) throw new CustomerError('No user found');
[err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
if(err) throw new CustomError('Error occurred while saving task');
if(user.notificationsEnabled) {
const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
if (err) console.error('Just log the error and continue flow');
}
}
上面的示例只是该解决方案的一个简单用例,您可以在to.js方法内附加拦截器,该方法将接收原始错误对象,对其进行记录或在将其传回之前进行任何必要的处理。
这篇文章只是查看异步/等待错误处理的另一种方式。不应将它用作您编写的每个异步/等待函数的goto,并且在很多情况下,将单个catch放在顶部就可以了。有时我们不想公开模型实现的错误对象,而是想提供一个掩盖底层猫鼬错误实现的自定义错误对象。
我们为此库创建了一个简单的NPM软件包,您可以使用以下程序进行安装:
Github Repo
npm i await-to-js
转自https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/