Back

JS asynchronous operation summary

Thu, Jul 04 20192 min read
avatar
Nathaniel

问题的起源

最近两天被这个异步给坑了好久,这里总结一下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执行完毕再调用BB执行完毕咋爱调用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的神器asyncawait

Async Await

ES2017给我们带来了神奇的asyncawait,它等于给标明async的自动加隐式Promise,即return的东西自动给你包在一个Promise里面,而await则自动从Promise里面取出结果,所以用asyncawait来写,上面的代码就变成了:
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里面使用,因此我在这里用了一个IIFEImmediately Invoked Function Expression,立即执行的函数,来把它包装起来,这样就大功告成了。不得不说asyncawait使生活更加轻松,大家能用asyncawait尽量用asyncawait吧,不要再掉入callback hell里面去了。总体来说JS的这一特性还是值得称赞的,但是跟C#AsyncAwait一比的话,还是有一点Naive,毕竟这个方式就是从C#借鉴而来,不过鉴于JS超强的跨平台兼容性,也算是各有所长啦。

解决方案

回到上面的问题,其中一个关键的问题是somefunc是一个第三方库,我们没法修改的,那怎么解决这个问题呢?答案是使用Node.js提供的神器Promisfy,它能把传统的CallbackPromisefunction统统变成asyncawait的形式,这样就再也不用头疼了,于是乎上述代码就变成了:
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'的结果。

Comments(0)

Continue with
to comment