es之Promise深刻理解

一、await和 async是幹什麼的

await操做符node

  • 等待一個Promise對象
  • 只能再async函數中使用
  • 若是操做符後的表達式不是一個Promise,則返該值自己
async function fun(){
    var val = await 20;
    console.log(val);
}
fun(); 
//20

async function bar(){
    var val = await Promise.resolve(20);
    console.log(val);
}
bar();
//20
複製代碼

async函數chrome

  • 定義異步函數,返回Promise實例,
  • 當 async 函數返回一個值時,Promise 的 resolve 方法負責傳遞這個值
  • 當 async 函數拋出異常時,Promise 的 reject 方法會傳遞這個異常值
async function bar(name){
    var subName = await Promise.resolve('fen');
    return name + subName;
}
bar('li').then((res)=>{
    console.log(res);
});

//lifen
複製代碼

二、Promise的鏈式then()是怎樣執行的

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log('p1_1')
}).then(() => {
    console.log('p1_2')
}).then(() => {
    console.log('p1_3')
});

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log('p2_1')
}).then(() => {
    console.log('p2_2')
}).then(() => {
    console.log('p2_3')
});
複製代碼

結果以下:segmentfault

p1_1
p2_1
p1_2
p2_2
p1_3
p2_3
複製代碼

Promise是基於微任務的(微任務文章點擊連接),async/await可視爲 Promise 的語法糖,一樣基於微任務實現promise

分析

  • Promise 多個 then() 鏈式調用,並非連續的建立了多個微任務並推入微任務隊列,由於 then() 的返回值必然是一個 Promise,然後續的 then() 是上一步 then() 返回的 Promise 的回調
  • 傳入 Promise 構造器的執行器函數內部的同步代碼執行到 resolve(),將 Promise 的狀態改變爲Promise {: undefined},而後 then 中傳入的回調函數 console.log('p1_1') 做爲一個微任務被推入微任務隊列
  • 第二個 then() 中傳入的回調函數 console.log('p1_2') 此時尚未被推入微任務隊列,只有上一個 then() 中的 console.log('p1_1') 執行完畢後,console.log('p1_2') 纔會被推入微任務隊列

總結

  • Promise的then()鏈式調用,是經過then() 返回一個新的Promise實現的
  • 若是 Promise 的狀態是 pending,那麼 then 會在該 Promise 上註冊一個回調,當其狀態發生變化時,對應的回調將做爲一個微任務被推入微任務隊列
  • 若是 Promise 的狀態已是 fulfilled 或 rejected,那麼 then() 會當即建立一個微任務,將傳入的對應的回調推入微任務隊列

三、Promise.resolve()與new Promise(resolve=>(resolve))的區別

EnquequeJob存放兩種類型的任務, 即PromiseResolveThenableJob和PromiseReactionJob, 而且都是屬於microtask類型的任務。瀏覽器

PromiseReactionJob: 能夠通俗的理解爲promise中的回調函數 PromiseResolveThenableJob(promiseToResolve, thenable, then): 建立和 promiseToResolve 關聯的 resolve function 和 reject function。以 then 爲調用函數,thenable 爲this,resolve function和reject function 爲參數調用返回。bash

參數爲thenable對象

Promise中resolve一個thenable對象,執行步驟如何? 在 Promise 中 resolve 一個 thenable 對象時,異步

  • 須要先將 thenable 轉化爲 Promsie,
  • 而後當即調用 thenable 的 then 方法

這個過程須要做爲一個 job 加入微任務隊列,以保證對 then 方法的解析發生在其餘上下文代碼的解析以後async

let thenable = {
    then(resolve, reject) {
        console.log('in thenable');
        resolve(100);
    }
};
var promiseB = new Promise((resolve) => {
    console.log('promiseB');
    resolve(thenable);
});
//或var promiseB = Promise.resolve(thenable);

var promiseA = new Promise((resolve) => {
    console.log('promiseA');
    resolve();
});

promiseB.then((res) => {
    console.log('out thenable ' + res)
}).then(() => {
    console.log('then2')
}).then(() => {
    console.log('then3')
});
promiseA.then(() => { 
    console.log(1);
}).then(() => { 
    console.log(2) 
}).then(() => { 
    console.log(3)
});
複製代碼

結果以下:函數

promiseB
promiseA
in thenable
1
out thenable 100
2
then2
3
then3
複製代碼

分析

in thenable 後於 promiseA 而先於 1 輸出,同時out thenable 100 在 1 後輸出。post

  • 一、同步任務中,promiseB中先(輸出promiseB)promiseB中resolve被調用,同時向microtask插入任務PromiseResolveThenableJob。promiseA中(輸出promiseA),同時向microtask插入任務1
  • 二、在 PromiseResolveThenableJob 執行中會執行 thenable.then(),從而註冊了另外一個微任務out thenable。執行任務1(輸出1),同時向microtask插入任務2。
  • 三、執行任務out thenable(輸出out thenable 100),同時向microtask插入任務then2。執行任務2(輸出2),同時向microtask插入任務3 。
  • 四、執行任務then2(輸出then2),同時向microtask插入任務then3。執行任務3(輸出3)。
  • 五、執行任務then3(輸出then3),至此結束。

Promise.resolve(thenable)與new Promise(resolve=>(resolve(thenable)))的處理結果一致

正是因爲規範中對 thenable 的處理須要在一個微任務中完成,從而致使了第一個 Promise 的後續回調被延後了1個時序

參數是一個 Promise 實例

  • 一、因爲 Promise 實例是一個對象,其原型上有 then 方法,因此這也是一個 thenable 對象。
  • 二、一樣的,瀏覽器會建立一個 PromiseResolveThenableJob 去處理這個 Promise 實例,這是一個微任務。
  • 三、在 PromiseResolveThenableJob 執行中,執行了 Promise.prototype.then,而這時 Promise 若是已是 resolved 狀態 ,then 的執行會再一次建立了一個微任務
var promiseA = Promise.resolve('promiseA');

var promiseB = new Promise((resolve) => {
    resolve(promiseA);
});
//或var promiseB = Promise.resolve(promiseA);
promiseB.then(() => { 
    console.log('then1') 
}).then(() => { 
    console.log('then2') 
}).then(() => { 
    console.log('then3') 
});

promiseA.then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
});
複製代碼

結果

//new Promise
1
2
then1
3
then2
then3
複製代碼
//採用或的結構Promise.resolve(promiseA)
then1
1
then2
2
then3
3
複製代碼

分析new Promise(resolve=>(resolve(promiseA)))狀況

'then1'輸出晚了兩個時序。對於參數是一個promise ,resolve(promiseA)究竟如何工做的?

例子中,當promiseB被resolved的時候, 也就是將一個promise(代指A)當成另一個promise(代指B)的resolve參數,會向EnquequeJob插入一個PromiseResolveThenableJob任務。PromiseResolveThenableJob大概作了以下的事情:

() => { 
  promiseA.then(
    resolvePromiseB, 
    rejectPromiseB
  );
}
複製代碼

而且當resolvePromiseB執行後, promiseB的狀態才變成resolve,也就是B追隨A的狀態。

  • 一、promiseA處於resolve狀態,promiseB中resolve被調用,同時向microtask插入任務PromiseResolveThenableJob
  • 二、promiseA.then被調用, 向microtask插入任務1
  • 三、執行PromiseResolveThenableJob, 向microtask插入任務resolvePromise(如上面的promiseA.then(...))
  • 四、執行 任務1(即輸出1),返回一個promise, 向microtask插入任務2
  • 五、執行resolvePromise, 此時promise終於變成resolve, 向microtask插入任務then1
  • 六、執行任務2(即輸出2)
  • 七、執行任務then1(即輸出then1),返回一個promise,向microtask插入任務then2
  • 八、執行任務then2(即輸出then2),返回一個promise,向microtask插入任務then3,執行任務then3(即輸出then3)

分析Promise.resolve(promiseA)狀況

三、await/async函數和Promise怎麼轉換

async function async1() {
    console.log('async1')
    await async2();
    console.log('async1 end')
}
    
async function async2() {
    console.log('async2');
}
async1();
    
new Promise(function (resolve) {
    console.log('promise')
    resolve();
}).then(function () {
    console.log(1);
}).then(function () {
    console.log(2);
}).then(function () {
    console.log(3);
})

複製代碼

瀏覽器chrome76 環境中,結果以下:

//瀏覽器chrome76 
async1
async2
promise
async1 end
1
2
3
複製代碼

node環境中,async1 end被推遲了2個時序

//node v10.6.0,
async1
async2
promise
1
2
async1 end
3
複製代碼

以瀏覽器運行結果來轉換

function async1(){
  console.log('async1 start');
  const p = async2();
  return Promise.resolve(p).then(() => {
      console.log('async1 end')
  });
}
    
function async2(){
  console.log('async2');
  return Promise.resolve();
}
//其餘代碼同上
複製代碼

總結

  • Promise.resolve(v) 不等於 new Promise(resolve => resolve(v)),由於若是 v 是一個 Promise 對象,前者會直接返回 v,然後者須要通過一系列的處理(主要是 PromiseResolveThenableJob)
  • 宏任務的優先級是高於微任務的

參考連接: 使人費解的 async/await 執行順序

「譯」更快的 async 函數和 promises

相關文章
相關標籤/搜索