继续整理一点对 JS
知识点的理解。或许一篇算一股,集齐八篇就能召唤点什么了(?)
作为一般通过前端,某种程度可以说这种技术本身就是随着性能飞升的红利崛起的,很多时候很少会考虑到靠近底层的东西。所以理解可能还是非常有限。然而,我写这个文章主要还是想到「把知识点讲一遍」是最好的复习方式,于是就用这个办法讲给自己。
一些事件是异步的。比如计时、与服务器交互…,它们可能需要等待一定的时间或者带来延迟。在它们面前,你有这么高速运转的同步事件(指立即执行),它不会默认就按照代码的先后顺序来等异步事件结束。所以问题来了:
- 怎么处理异步?
- 这些异步事件是怎么延迟的,记住它给出的原理(逃,这下还真是,读代码题已经品鉴的够多了。。。。
怎么处理异步?
看 JavaScript.info,提到第 11
章的时候出现了三种不同的处理异步的方法:callback
、promise
和 async / await
,但是品鉴这种东西的时候基本上很少见到
callback。
原因其实也很简单,它可以完美的产生回调地狱。
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
| function loadScript(src, callback) { let script = document.createElement('script'); script.src = src;
script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script); }
loadScript('1.js', function(error, script) {
if (error) { handleError(error); } else { loadScript('2.js', function(error, script) { if (error) { handleError(error); } else { loadScript('3.js', function(error, script) { if (error) { handleError(error); } else { } }); } }); } });
|
或者可以拆开嵌套,使用一个个独立的顶层函数来操作。但是会给人一种「支离破碎的思考·发言」的感觉,每一步都要被拆成一个一次性函数,产生了混乱的命名空间(激活死亡
buff,coding 当中最讨厌的事情 —— 起名)。
所以「生产者 - 消费者模式」的 Promise
加入战斗。它的语法相对简单,而且可以链式调用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| let promise = new Promise((resolve, reject) => { (condition) ? resolve(_result): reject(_reason); reject(reason); }).then((result, reason) => { doSomething(result); doSomething(reason); }).catch(() => { console.log(reason); }).finally(() => { ... })
|
resolve 的值会变成下个阶段的 result。当然 then
函数的参数可以为空,就单纯是上一个阶段的 then。
但是,这种来回 resolve 和 reject
的代码还是显得笨重。于是有了语法糖,几乎用起来和同步函数没有太大差别的
async
和 await
。
1 2 3 4 5 6
| async function f1(){ return 1; }
f1().then(res => console.log(res));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function f2(){ return new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000); }); }
async function afterf2(){ let v = await f2(); console.log(v); }
|
1 2 3 4 5 6 7 8
| async function f3() { await Promise.reject(new Error("Whoops!")); }
async function f3() { throw new Error("Whoops!"); }
|
从回调函数到 async /
await,这个流程逐渐变得清晰起来。但它到底是怎么运作的呢?
事件是如何延迟的
先从一道经典的题开始吧,这种东西真的品鉴的太多了。。。。
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
| console.log('script start')
const async1 = async () => { await async2() console.log('async1 end') } const async2 = async () => { console.log('async2 end') }
async1()
setTimeout(() => { console.log('setTimeout') }, 1000)
new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') })
console.log('script end')
|
1 2 3 4 5 6 7 8 9 10
| script start async2 end Promise script end
async1 end promise1 promise2
setTimeout
|
毕竟异步事件 then 和 await 会慢一点,它出现在同步输出的后面。但为什么
setTimeout 会在最后?
这就要引入 JS 处理这些事件的一个机制了 —— 事件循环。
在 JS
当中,事件被分为同步和异步事件,而异步事件也分为宏任务和微任务。
· 宏任务:加载 script,setTimeout / setInterval,IO
操作
· 微任务:async await,then …
一般的事件会进入执行栈当中,而异步事件存在两个队列。
这个题目的执行顺序是这样的:
- 前四个输出都来源于同步任务。创建 async 函数和 promise
的执行语句并不带来延迟。
- await 和 then
是异步微任务。当它等待完毕的时候,会把它执行的代码语句推入微任务队列当中。两个
await 一个 then 后面的
console.log
进入微任务队列。
- 1000ms 后(哪怕 0ms
也是这样,微任务总会优先),
setTimeout
执行的微任务队列把 console.log
推进宏任务队列。
- 微任务后面的内容依次入栈并执行。(并不是微任务全部入栈后执行,栈里每次还是只有一个代码,它们还是会按顺序的)。输出
async1 end、promise1、promise2。
- 没有微任务了,轮到宏任务队列。输出
setTimeout。
同时,JS 允许我们自己调用微任务。
1 2 3
| queueMicrotask(() => { console.log('Where am I?'); })
|
同理,把它放在 async1 和 Promise
的中间,它会出现在两个微任务的中间。
那如果,我让它嵌套起来如何?
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
| console.log('script start')
const async1 = async () => { await async2() console.log('async1 end') } const async2 = async () => { console.log('async2 end') }
async1()
setTimeout(() => { console.log('setTimeout') })
queueMicrotask(() => { console.log('set Queue') queueMicrotask(() => { console.log('set Queue again') }) })
new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') })
console.log('script end')
|
异步的部分会这样执行。
1 2 3 4 5
| async1 end set Queue promise1 set Queue again promise2
|
宏任务会被拖住。所以如果微任务不断地循环,宏任务根本没有执行的可能,就卡在这里了。