notes on async/await

大家好,又到了新一期“扯蛋”时间了。

我几乎没怎么用过 callbackspromisegeneratoryield 这些听起来牛逼哄哄的东西。我直接来看 async/await。有这么多前辈高人,我这小菜就捡了便宜,越过了这一系列发展过程中的酸甜苦辣,直接去偷取“果实”。还有我看CSS的时候,是无视了IE6这个可怕时代,直接看CSS3的知识。这也就是大家所说的速成,没什么内力,没经历过,就不明白现在为啥是这个样子,不扯了,去吃完扯面再说。

我先是看了边城理解 JavaScript 的 async/await,受益匪浅;接着又找到了allen_heasync/await 执行顺序详解,这篇文章就是我想要的,看下面的评论,不管测试结果,我要的就是这篇的解释和这个思路。我瞅了一眼阮一峰Node 定时器详解,这和浏览器中的Event loop还是有很多不同之处,尤其每一轮事件循环中要干的活是大大的不同。

此篇仅仅是一个开始,还需投入精力加深理解。

  1. 让我们测试几个代码,学习研究之用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log("script start...");

setTimeout(()=>console.log("next loop, obviously!"));

function ss() {
console.log("I'm ss");
return "ss";
}

async function test() {
console.log("test start...");
const r = await ss();
console.log(`r: ${r}`);
console.log("test end...");
}

test();

Promise.resolve('how to put a promise to the microtask queue?').then(console.log, console.error);

console.log("script end...");

结果在此,请不要偷看,O(∩_∩)O

script start…
test start…
I’m ss
script end…
r: ss
test end…
how to put a promise to the microtask queue?
next loop, obviously!

  1. 略微改动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log("script start...");

setTimeout(()=>console.log("next loop, obviously!"));

async function ss() {
console.log("I'm ss");
return "ss";
}

async function test() {
console.log("test start...");
const r = await ss();
console.log(`r: ${r}`);
console.log("test end...");
}

test();

Promise.resolve('how to put a promise to the microtask queue?').then(console.log, console.error);

console.log("script end...");

script start…
test start…
I’m ss
script end…
how to put a promise to the microtask queue?
r: ss
test end…
next loop, obviously!

分析一下

这两段代码几乎一毛一样。仅仅是 await 那里,第一次等了个 Function call,第二次等了 AsyncFunction call。说白了,第一次等待返回的不是Promise的值 “ss”;第二次等待返回的是一个Promise的值 #value:”ss”, status:”resolved”#。(这里用词不是很准确,比如 Promise的值,你懂得就好。至于第二个值,只是意思意思表示一下,大概就那样子,没有字面量表示法。)

这个 await 还是很骚的。

  • 第一次,哎呀,是个字符串,不是Promise的值,舍不得交出去,等等吧。跳出了test的执行(注意是跳出去了,暂停了,执行的现场还是保存了)。执行完后面的几句代码,然后搂不住了,await 只好把东西交出来,也就是继续执行。完事,没码可执行,就把 microtask queue 清空了。最后到下一轮task了。

  • 第二次,这货更加过分,因为是个Promise,这货就是不肯交出来。直到 microtask queue 清空了,才知道大势已去,也只能返回这个Promise的值,赶紧执行剩余代码。新的一轮工作又开始了。

简单说就是在 AsyncFunction call 中 await expression, expression的值算出来以后(这里也很讲究,expression的计算,如果放在主线程,还是要卡住,像ajax比较适合,反正不在主线程计算,马上能返回,最后给个结果就行),await 拿到值,现在是不会交出来的;
而且还要对别人说:“滚,离我远点。”,没办法,只能暂时跳出这个 AsyncFunction 了。执行后面的代码。完事后,这里也是一个关键点。如果 await 拿的不是一个 Promise的值,那它就要交出来,AsynFunction call得以继续执行,接着清 microtask queue;但如果 await 手里拿的是一个 Promise的值,不管是pending状态还是完蛋状态,都会先清 mircotask queue,它会死皮赖脸的,要等 Promise 计算完后(非pending状态) ,才肯交出那个值,继续从暂停的地方执行。

当然,这个流程不是 await 控制的,只是把 await 拿到第一人称比较形象。这货就是个拖啊,先拖住再说。 await expression,expression计算出来以后,会有一跳,这就改变了代码的执行流程;待后来,它王者归来,返回胜利的果实。所以,它有自己的适用范围。你确实什么都可以给它 await 一下,但你从这个执行流程也可以看出来,你 await 的任务,如果要在主线程执行,也是没啥卵用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
**含有伪代码,慎重
*/

jobs();//简单任务

async function B2() {
let p = await new Promise(r=>{
sleep(一天);
setTimeout(()=>{
sleep(一天);
r("呵呵")
},一天);
});
console.log("p: " +p);
}

B2();

Steve_Jobs();//复杂计算
/**/

看吧,await 后面的先要睡一天,setTimout就几毫秒吧(我机子,console.time('sT');setTimeout(function(){},1000);console.timeEnd('sT');,cpu G3220,Chrome71 0.05 ~ 0.13 ms, node v10.4.0 0.06 ~ 0.95 ms,交互式不超过0.1ms,文件式就比较耗时,但也没有超过1ms;仅做参考,机器及环境相关),await拿到Promise(是pending状态)。出去算完 Steve_Jobs。 一天后,setTimeout任务来了,可是它又睡了一天,然后 Promise 就resolved了。此时 p 值才得以打印。流程就是这样。或许浏览器会直接让你滚蛋,他么的什么破代码,想卡死我?。nodejs上我也没有试过。

当然,小弟的栗子不好吃,不太恰当。

在下在上篇文章中,也说了,不爱提什么同步异步的。如下片段,

1
2
3
r1 = await job1();
r2 = await job2(r1);
r3 = await job3(r2, r1);

异不异步我不管,你口中的异步任务,还是按次序来。重要的是次序。

不过,

1
2
3
let r = await ajax(some-url); 
callback(r);
//...

确实比

1
2
ajax(some-url, callback);
//...

看起来舒服多了。尤其是你安排很多后续任务,才会有真正的优势。本篇的重点是刻画一下 await 这个货。具体怎么使用,我也不会。拜拜,我们下期再见。

刚才又想了想,有些地方不对

上面吹了那么多牛,后来,我发现,自己不能解释下面的代码,次序都是 step1->step2->step3;可是两段代码的互相穿插,穿插的顺序呢? await 到底和 then 之间有着怎样的爱恨情仇?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function takeLongtime(n) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(n + 200), n);
})
}

function step1(n) {
console.log(`step1: ${n}`);
return takeLongtime(n);
}

function step2(n) {
console.log(`step2: ${n}`);
return takeLongtime(n);
}

function step3(n) {
console.log(`step3: ${n}`);
return takeLongtime(n);
}

console.time('sT1');
step1(400)
.then(step2)
.then(step3)
.then(v=>{
console.log(v);
console.timeEnd('sT1');
});

async function af(){
console.time('sT2');
const n = 400;
const r1 = await step1(n);
const r2 = await step2(r1);
const r3 = await step3(r2);
console.log(`r3: ${r3}`);
console.timeEnd('sT2');
}

af();

这里我搞不清的是下面的问题:

  1. function cb(n) {}; setTimeout(cb, 5000, 1234);,当setTimeout执行了,那么可以认为Timer线程在五秒后会把 cb 及其参数 1234 打入冷宫,不,打入事件队列。主线程有空就执行一下。
  2. 那么
    new Promise(r=>{ setTimeout(()=>r("呵呵"), 5000) }).then(console.log, console.error);,它是如何执行的?
  • new Promise,首先setTimeout执行了,返回一个 pending 状态的 Promise 对象。(五秒后Timer线程会放 ()=>r('呵呵') 进入事件队列;当主线程执行了这个,Promise对象就会resolved,值就是 “呵呵”。)
  • 我想问的是,new Promise这个构造执行完,返回 pending 状态的 Promise 对象,然后呢?然后干什么?(离开这里,往下执行?将来再回来?)那后面的then也不能不管吧?既然是 pending,那也不能把后面 then 中的两个任务某一个放入 microtask queue 吧?应该是其他线程接管了。这个先放一放。

我又来装逼了

老阮的文章,其中还有朴灵的批注。啥也不说,看了看评论。只是觉得太浮躁了。细思极恐。披着各种外衣的。打着追求真理的幌子。所谓的概念不是自己的理解吗?不断提升吗?就一下子想得到正确的概念,正确的答案?真能一下子得到正确的概念,这个世界就简单了,还要学习吗?或者,哪有什么真理,你真的想过?写操作系统,写v8的那些人,代码还在不断更新,为啥不是一出来就是完美的?哪些可是真的大神啊。在哥德尔手里,数字能推翻公理化;在我手里也只能加加减减卡里剰几毛钱了。什么是“横看成岭侧成峰”,成天背几个概念,顶个毛用。线程咋咋,进程咋咋,你知道线程怎么表示?数据结构有多少字段?(sorry,我不知道)背那么清楚还不如踢足球?

1
2
3
window.isNaN(Object)
Number.isNaN(Object)
2**53 === 2**53 + 1

talk is cheap,show me the code。哈哈。

书是看不完的,得有自己的理解,思维。偷偷打基础,来日可与群雄一战。技术,知识将来才是真正的战场。