写JavaScript最头疼的就是回调函数,反人类的设计,虽然IO异步机制很牛逼,于是各种轮子开始造起来,目的是让你尽量以同步IO的方式编写异步IO程序,这样的代码易于维护,并且还能保持IO异步的特性。
其中,async/await
就是其中一个轮子,这货目前看起来还是比较受欢迎的,我也经常在使用。今天要正好也要用它发现不工作了,大致代码如下:
app.js
const numbers = [33, 41, 57];
let sum = 0;
const sumFunction = (a, b) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 1000);
});
};
numbers.forEach(async (number) => {
sum = await sumFunction(sum, number);
});
console.log(sum);
跑一下
$ node app.js
0
以上输出为0,超出了我们的预期,显然sum在没有算好之前就先打印出来了。Array.prototype.forEach
是原始代码,看不到源码,不知到它怎么实现的,所以我大胆猜测它可能是这么实现的:
Array.prototype.forEach = function (callback) {
for (let index = 0; index < this.length; index++) {
callback(this[index], index, this);
}
};
因为没有await callback
,所以我们在forEach里的await就没生效,解决这个问题有两种方式:
for替换forEach
添加一个包裹方法wrapFunc
,然后在包裹方法加async。如
app.js
const numbers = [33, 41, 57];
let sum = 0;
const sumFunction = (a, b) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 1000);
});
};
const wrapFunc = async () => {
for(let i = 0; i < numbers.length; i++) {
sum = await sumFunction(sum, numbers[i]);
}
console.log(sum);
};
wrapFunc();
以上测试结果
$ node app.js
131
封装一个forEach
我们也可以扩展Array.prototype
一个自定义的forEach来实现它也是可以的,比如这个方法就叫做asyncForEach
app.js
const numbers = [33, 41, 57];
let sum = 0;
Array.prototype.asyncForEach = async function (callback) {
for (let index = 0; index < this.length; index++) {
await callback(this[index], index, this);
}
};
const sumFunction = (a, b) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 1000);
});
};
const wrapFunc = async () => {
await numbers.asyncForEach(async (number) => {
sum = await sumFunction(sum, number);
});
console.log(sum);
};
wrapFunc();
再次测试结果没问题
$ node app.js
131
虽然await/async能解决问题,但看着感觉还是有点不舒服,也不能要求这么高了,这些年ECMAScript不断更新推出一些标准想努力改善回调函数嵌套过深的问题,说不定几年后又推出什么大招就更好了。