上周由于工作忙和周末头疼,最终没能发布这篇。现在抓紧时间弥补上。
原文:Asynchronous JavaScript: From Callback Hell to Async and Await 作者:DEMIR SELMANOVIC
编写一个成功的Web应用程序的关键之一是能够每页打几十个AJAX调用。
这是一个典型的异步编程挑战,您如何选择处理异步调用,在很大程度上,会导致或破坏您的应用程序,并且可能是您的整个启动。
在很长一段时间内,在JavaScript中同步异步任务是一个严重的问题。
这个挑战正在影响使用Node.js的后端开发人员以及使用任何JavaScript框架的前端开发人员。异步编程是我们日常工作的一部分,但是这个挑战经常被忽略,而不是在正确的时间考虑。
第一个也是最直接的解决方案是以嵌套函数的形式作为回调。这个解决方案导致了所谓的回调地狱,而且太多的应用程序仍然感到它的燃烧。
然后,我们有了Promises。这种模式使代码更容易阅读,但与“不要重复自己”(DRY)原则相去甚远。仍然有太多的情况下,你不得不重复相同的代码段来正确管理应用程序的流程。async / await语句形式的最新补充最终使JavaScript中的异步代码像其他任何代码一样易于读写。
让我们来看看每个解决方案的例子,并反思JavaScript中异步编程的发展。
为此,我们将检查执行以下步骤的简单任务:
对这些调用进行同步的古老解决方案是通过嵌套回调。对于简单的异步JavaScript任务来说,这是一种不错的方法,但是由于一个名为回调地狱的问题而无法扩展。
三个简单任务的代码如下所示:
const verifyUser = function(username, password, callback){
dataBase.verifyUser(username, password, (error, userInfo) => {
if (error) {
callback(error)
}else{
dataBase.getRoles(username, (error, roles) => {
if (error){
callback(error)
}else {
dataBase.logAccess(username, (error) => {
if (error){
callback(error);
}else{
callback(null, userInfo, roles);
}
})
}
})
}
})
};
每个函数都得到一个参数,这个参数是另一个函数,它的参数是前一个动作的响应。
太多的人仅仅通过阅读上面的句子就会体验到大脑冻结。拥有数百个类似代码块的应用程序将给维护代码的人带来更多的麻烦,即使他们自己编写代码。
一旦你意识到database.getRoles是嵌套的回调的另一个函数,这个例子变得更加复杂。
const getRoles = function (username, callback){
database.connect((connection) => {
connection.query('get roles sql', (result) => {
callback(null, result);
})
});
};
除了难以维护的代码之外,DRY原则在这种情况下完全没有价值。例如,在每个函数中重复错误处理,并且从每个嵌套函数调用主回调。
更复杂的异步JavaScript操作(例如通过异步调用进行循环)是一个更大的挑战。事实上,用回调来做这件事并不是一件容易的事情。这就是为什么像蓝鸟和Q这样的JavaScript Promise库获得如此多的关注。它们提供了一种对语言本身尚未提供的异步请求执行常见操作的方法。
这就是原生JavaScript Promises 进来的原因。
Promises是逃避回调地狱的下一个合乎逻辑的步骤。这个方法并没有去掉回调函数的使用,但是它使得函数的链接简单明了,简化了代码,使得它更容易阅读。
有了Promise,我们的异步JavaScript示例中的代码将如下所示:
const verifyUser = function(username, password) {
database.verifyUser(username, password)
.then(userInfo => dataBase.getRoles(userInfo))
.then(rolesInfo => dataBase.logAccess(rolesInfo))
.then(finalResult => {
//do whatever the 'callback' would do
})
.catch((err) => {
//do whatever the error handler needs
});
};
为了达到这种简单性,例子中使用的所有函数都必须是Promisified的。让我们来看看如何getRoles
更新方法来返回一个Promise
:
const getRoles = function (username){
return new Promise((resolve, reject) => {
database.connect((connection) => {
connection.query('get roles sql', (result) => {
resolve(result);
})
});
});
};
我们已经修改了方法来返回一个Promise
带有两个回调函数的方法,并且它Promise
自己执行方法中的操作。现在,resolve
和reject
回调将被映射到Promise.then
和Promise.catch
分别的方法。
您可能会注意到,这种getRoles
方法仍然是内部倾向于厄运现象的金字塔。这是由于数据库方法的创建方式,因为它们不会返回Promise
。如果我们的数据库访问方法也返回,Promise
那么getRoles
方法如下所示:
const getRoles = new function (userInfo) {
return new Promise((resolve, reject) => {
database.connect()
.then((connection) => connection.query('get roles sql'))
.then((result) => resolve(result))
.catch(reject)
});
};
JavaScript默认是异步的。这可能是为什么花费这么长时间才能获得在JavaScript中正确运行的同步代码的原因。但是,迟到比从未更好!厄运的引入极大地缓解了厄运的金字塔。不过,我们仍然需要依靠传递给的回调函数.then
和.catch
方法Promise
。
承诺为JavaScript中最酷的改进之一铺平了道路。ECMAScript 2017在JavaScript中以Promises的形式async
和await
语句引入了语法糖。
它们允许我们编写Promise
基于代码的代码,就好像它是同步的,但是不会阻塞主线程,因为此代码示例演示了:
const verifyUser = async function(username, password){
try {
const userInfo = await dataBase.verifyUser(username, password);
const rolesInfo = await dataBase.getRoles(userInfo);
const logStatus = await dataBase.logAccess(userInfo);
return userInfo;
}catch (e){
//handle errors as needed
}
};
等待Promise
解决只允许在必须定义的async
函数内verifyUser
使用async function
。
然而,一旦这种小的变化是由你可以await
任何Promise
没有其他方法的其他变化。
异步函数是JavaScript中异步编程发展的下一个合理步骤。他们将使您的代码更清洁,更容易维护。声明一个函数async
将确保它总是返回一个,Promise
所以你不必担心这个问题了。
为什么你async
今天要开始使用JavaScript 函数?
try
/ catch
就像在其他同步代码中一样。.then
块内设置断点不会移动到下一个,.then
因为它只能通过同步代码。但是,您可以await
像呼叫同步一样通过呼叫。什么是 async和await?
Async/await语句是在JavaScript Promises之上创建的语法糖。它们允许我们编写基于Promise的代码,就好像它是同步的,但不阻塞主线程。
什么是回调地狱?
在JavaScript中,回调地狱是代码中的一种反模式,这是由于异步代码结构不良造成的。当程序员尝试在基于异步回调的JavaScript代码中强制使用可视化的自顶向下结构时,通常会看到这种情况。
什么是JavaScript promises?
JavaScript中的promise就像一个占位符值,预期最终将解决最终成功的结果值或失败的原因。
关于作者: Demir是一名开发人员和项目经理,在广泛的软件开发角色方面拥有超过15年的专业经验。他擅长作为独立开发人员,团队成员,团队负责人或多个分布式团队的经理。他与客户紧密合作,确定想法并交付产品。