相信很多使用过Node的人对于回调地狱非常熟悉,回调地狱就是回调函数的不断嵌套造成的,回调地狱造成的后果就是代码的可维护性以及可读性降低。为了解决回调地狱的问题,社区提出了很多解决方案,一开始由Promise
的方案,能够把回调函数抽出调用者来减少回调的层级,但并没有解决回调函数所带来的代码阅读困难的问题。这时候提出了 async/await
,最先提出 async/await
这种语法的是 C# 语言, ES 以其兼容并蓄的精神迅速将这种优雅的语法吸收进来。通过 async/await
可以将各种回调地狱转化为线性的代码,使得代码更容易阅读,也更容易维护。
这篇文章中所使用的是最原始的mysql模块,通过一步步的改造使其能够使用 async/await 语法写出线性的代码,前置代码如下
const mysql = require('mysql');
const connection = mysql.createConnection({
host: 'localhost',
user: 'uers',
password: 'password',
database: 'world'
})
connection.connect();
下面的回调写法也就是官方文档中的示例写法。
connection.query('SELECT NAME FROM COUNTRY', function(error, results, fields) {
if (error) throw error;
console.log('Get The Results', results);
});
这种写法在回调层级较少的情况下并没有明显弊端,但由于经典Node应用的异步都是通过回调函数实现的,所以但是当功能变得复杂的时候,回调层级一多,代码就会变得非常难看和非常难以维护。现在你可以尝试一下使用回调实现以下功能:当从数据库获取完数据之后,将其写入一个文件,写入成功后向远程服务器发送一个请求,接收到回应之后关闭这个应用。只要是一个有一定审美的程序员,面对这一坨}}}}}}}都会觉得恶心。所以我们需要改进。
Promise
是解决}}}}}}的一个方案,把上面的代码用Promise
改写之后如下所示
let promiseQuery = new Promise((resolve, reject) => {
connection.query('SELECT NAME FROM COUNTRY LIMIT 3', (error, results, fields) => {
if (error) reject(error);
resolve(results);
})
})
promiseQuery.then((results, fields) => {
console.log('The solution is : ', results);
})
Promise
可以看作一个管理回调函数的对象,通过.then()方法来传入回调函数,避免了回调函数过多嵌套的问题,变成了链式的.then()。所以Promise
并没有完全解决回调地狱的问题,Promise
只是将}}}}回调变成了.then.then.then回调,写出来的代码一样不好看,维护性也没有比原来的回调写法好多少。我们需要更加扁平化的写法。
async/await改造是基于Promise
的,保持上面的promiseQuery
不变,定义一个async function
,使用await
获取数据,对数据进行相关操作,然后执行这个async function
即可。
async function getData() {
const value = await promiseQuery;
return value;
}
(async function displayData () {
let value = await getData();
console.log(value);
})()
可以看出在async function
里面,回调已经变成了扁平化的代码,现在回看实现我们最初提出的功能:当从数据库获取完数据之后,将其写入一个文件,写入成功后向远程服务器发送一个请求,接收到回应之后关闭这个应用。只要是一个有一定审美的程序员,面对这一坨}}}}}}}都会觉得恶心。所以我们需要改进。通过MySql模块的async/await已经成功砍掉了一对{},只要加以努力,按图索骥,将其它的api使用async/await 进行改造,就可以砍掉一对又一对的{},实现扁平化和易维护的代码。
通过这篇文章,可以了解到,回调地狱形成的原因。然后通过Promise和async/await的改造,将存在回调地狱缺陷的代码改造为扁平化易维护的代码。抛砖引玉,按照文中的思路可以对其它的使用回调函数的代码进行改造。