继续整理一点对 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
   | 
 
宏任务会被拖住。所以如果微任务不断地循环,宏任务根本没有执行的可能,就卡在这里了。