JS asynchronous operation summary

问题的起源
最近两天被这个异步给坑了好久,这里总结一下Javascript的异步操作。
一切都源于下面这样结构的一段代码:
function main(){
console.log('test result: '
+ test()); }
const somefunc = (array, callback) => {
//the array is not used
//call the callback
setTimeout(() => {
callback();
}, 2000); }
function test(){
let res = 'default';
let a = 1, b = 2;
somefunc({},()=>{
if(a
> b){
res = 'error';
}else {
res = 'warning';
}
});
return res; }
main();
你认为test()执行完res是什么?结果不是error,也不是warning ,而是default,我也是服气了,后来才搞明白这就是所谓的none-blocking IO模型。就是说somefunc()还没有执行完,test()就return了,相当于开启了一个子进程去做somefunc的事情,使得二者并行进行了。然而JS是单线程的,因此就用了这种奇怪的异步的方式来进行。
所以我们要让它像同步代码的顺序一样执行的话,需要怎么办呢?
更简单的例子
上面的例子还是比较复杂了,下面举个更简单的例子:
const printLetter = letter => {
setTimeout(() => {
console.log(letter);
}, getRandom(1000,3000)); };
const getRandom = (max,min) => {
return Math.floor(Math.random()
* (max
- min))
+ min; };
printLetter('A'); printLetter('B'); printLetter('C');
这样三个printLetter的操作,怎样保证结果是按照A、B、C的顺序输出呢?
Callback
最传统的方案就是callback了,你问我callback是什么?正如其名,回调函数,就是你让A执行完毕再调用B,B执行完毕咋爱调用C不就完成了这个操作了?于是代码就变成了这样:
const printLetter = (letter,callback) => {
setTimeout(() => {
console.log(letter);
callback();
}, getRandom(1000,3000)); };
const getRandom = (max,min) => {
return Math.floor(Math.random()
* (max
- min))
+ min; };
printLetter('A',()=>{
printLetter('B',() => {
printLetter('C',()=>{});
}); });
功能倒是实现了,但是怎么看怎么别扭,这么简单个功能都要搞成这个样子,稍微复杂些岂不是很要命了?
这个时候我们就要看看ES6带来的法宝了
Promise
ES6引入了Promise的机制,什么是Promise?基本上就是给小朋友说等下有糖吃哦,然后看小朋友把作业完成了,很乖,就给他糖吃,给吃糖就是resolve,如果小朋友不写作业那当然你也可以reject就没有糖了,使用Promise.then()基本上就可以保证执行的顺序了,于是代码就变成了下面这样:
const printLetter = letter => {
return new Promise(resolve => {
setTimeout(() => {
console.log(letter);
resolve();
}, getRandom(1000,3000));
}); };
const getRandom = (max,min) => {
return Math.floor(Math.random()
* (max
- min))
+ min; };
printLetter('A').then(() => {
printLetter('B').then(() => {
printLetter('C');
}); });
好像有些逻辑了的样子,但是怎么还是怎么看怎么别扭啊??别急,我们还有ES2017的神器async和await
Async Await
ES2017给我们带来了神奇的async和await,它等于给标明async的自动加隐式Promise,即return的东西自动给你包在一个Promise里面,而await则自动从Promise里面取出结果,所以用async和await来写,上面的代码就变成了:
const printLetter = async letter => {
setTimeout(() => {
console.log(letter);
}, getRandom(1000,3000)); };
const getRandom = (max,min) => {
return Math.floor(Math.random()
* (max
- min))
+ min; };
(async () => {
await printLetter('A');
await printLetter('B');
await printLetter('C'); })();
嗯,好多了,这里由于await只能在async里面使用,因此我在这里用了一个IIFE即Immediately Invoked Function Expression,立即执行的函数,来把它包装起来,这样就大功告成了。不得不说async和await使生活更加轻松,大家能用async和await尽量用async和await吧,不要再掉入callback hell里面去了。总体来说JS的这一特性还是值得称赞的,但是跟C#的Async和Await一比的话,还是有一点Naive,毕竟这个方式就是从C#借鉴而来,不过鉴于JS超强的跨平台兼容性,也算是各有所长啦。
解决方案
回到上面的问题,其中一个关键的问题是somefunc是一个第三方库,我们没法修改的,那怎么解决这个问题呢?答案是使用Node.js提供的神器Promisfy,它能把传统的Callback和Promise的function统统变成async和await的形式,这样就再也不用头疼了,于是乎上述代码就变成了:
const util = require('util');
async function main(){
console.log('test result: '
+ await test()); }
const somefunc = (array, callback) => {
//the array is not used
//call the callback
setTimeout(() => {
return callback();
}, 2000); }
async function test(){
let res = 'default';
let a = 1, b = 2;
await util.promisify(somefunc).bind(this)({});
if(a
> b){
return 'error';
}else {
return 'warning';
} }
main();
这一次代码终于按照正确逻辑执行了,得到了res='warning'的结果。