JavaScript的异步

协程

和lua中的协程十分相似。但也有一些细微的差别。
首先我们需要明确的一个概念是:协程不是线程。
线程可以看成是系统的子程序,协程呢,可以理解为线程的子程序。协程和主程序在一个线程里面交替执行。控制权由程序自己控制。

先看一个例子,然后在解释它是怎么执行的吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. var testCoroutine = function*(param) {
2. console.log('Hello!'+param);
3. var x = yield "第一次yield返回"+param;
4. console.log('再次执行协程得到的输入: ' + x + ",param:"+param);
5.
6. var y = yield "第二次yield返回"+param;
7. console.log('再次执行协程得到的输入: ' + y + ",param:"+param);
8. return "结束"
9. }

cp = testCoroutine("调用1")//创建协程函数对象
console.log("=========start===========")
console.log(cp.next()) //第一次调用
console.log(cp.next("调用2"))//第二次调用
console.log(cp.next("调用3"))//第三次调用

得到的输出如下:

1
2
3
4
5
6
7
=========start===========
Hello!调用1
{ value: '第一次yield返回调用1', done: false }
再次执行协程得到的输入: 调用2,param:调用1
{ value: '第二次yield返回调用1', done: false }
再次执行协程得到的输入: 调用3,param:调用1
{ value: '结束', done: true }

输出日志中带有{}表示的是子程序返回的结果。value是返回的值,done是表示子程序是否执行结束。
第一次创建协程函数对象的时候,发生了什么事情?
首先函数在第一次被调用的时候,会发生变化,变成一个Generator的迭代器。因为他使用了function*的语法编写。

通过调用next函数,Generator函数将执行,直到遇到yield关键字返回。下次再调用next,Generator函数会从上次返回点继续执行。

所以结合上面的日志,就能看出来,cp = testCoroutine("调用1")这一步没有让函数执行。接下来第一次调用才真的开始执行。到第三行结束返回。此时yield立即返回。此时x连执行都没有。然后第二次调用开始,此时x有值了。是cp.next("调用2")的参数调用2。然后到第6行又被yield返回。以此类推。

这个和lua的协程很像。但是lua协程不是使用next执行,而是使用resume唤醒子程序。具体可以结合我的这篇文章一起使用。

Promise和async/await

我们知道JavaScript实现异步的方法,最常用的并不是协程而是Promise。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const waitTimePromise = (msg,seconds) => {
return new Promise((resolve, reject)=>{
setTimeout(()=> {
console.log(msg)
resolve(`等待${seconds}s`)
}, seconds * 1000)
})
}


waitTimePromise("waitTimePromise第一次调用",1).then((result)=>{
console.log("第一个then的输出,Promise的输出"+result)
return waitTimePromise("waitTimePromise第二次调用",2)
})
.then((result)=>{
console.log("第二个then的输出,Promise的输出"+result);
}).catch((err)=>{
console.log(err);
})

输出结果:

1
2
3
4
waitTimePromise第一次调用
第一个then的输出,Promise的输出等待1s
waitTimePromise第二次调用
第二个then的输出,Promise的输出等待2s

Promise的这种链式执行方法,极大的降低了异步理解的成本。但是如果有很多任务需要执行,就会有一堆then的执行语句。代码不清晰。因此JavaScript又推出了包装了Promise的语法糖。async/await.
async修饰一个函数。就可以将一个函数变成一个返回成一个Promise类型的函数。这样上面的代码就可以改成下面的样子。

1
2
3
4
5
6
async executefuncs(){
await dosomething1();
await dosomething2();
await dosomething3();
...
}

await只能在async修饰的函数中使用。一旦出现await就是暂停子程序的执行,转而执行主程序。直到await等待的任务执行完成。又继续下一行执行。
但是这样有个麻烦的地方就是每一行都在等待。上面三个await,假设每个await需要2s。就要阻塞6s。
有什么好办法解决吗?还真的有。那就是将函数返回先赋值给变量。然后await变量。此时三个await就只总共需要2s。

1
2
3
4
5
6
7
8
9
10
async executefuncs(){
let p1 = dosomething1();
let p2 = dosomething2();
let p3 = dosomething3();

await p1;
await p2;
await p3;
...
}

总的来说async/await相比Promise更灵活一些。代码也简洁很多。但是本质上还是Promise。所以对Promise了解更多,才能更好的使用async/await。

好了,下次再来说说c++的Promise吧。