JavaScript中異步與同步,回調函數,Promise ,async與await之滿漢全席

1、異步與同步

理解異步與同步

一、在生活中,按照字面量來理解,異步指的是,好比我先吃完蘋果再看電視,這是咱們生活中理解的異步。同步就是我邊吃蘋果邊看電視。 html

二、然而,對咱們的電腦程序來講,同步與異步的概念偏偏跟咱們在生活中的理解徹底相反。同步指的是我先吃完蘋果,而後再看電視(執行完一個事件再去執行另外一個事件,若上一個事件沒執行完,下一個事件就沒法執行);異步指的是我邊吃蘋果邊看電視(多個事件能夠同時執行,不會發生阻塞)。理解js中的異步和同步,若是對這兩個概念混淆了,你只要想到跟咱們生活中異步與同步的理解相反就好了。ajax

談談JavaScript的單線程機制

單線程是JavaScript中一個比較重要的特性。這就表示在同一個時間只能作一件事情。數據庫

那爲何JavaScript不弄成多線程的呢?還能更加充分利用CPU呢。segmentfault

這要從JavaScript的使用場景提及,JavaScript做爲瀏覽器語言,主要用途是經過操做DOM,與用戶進行交互。後端

咱們設想一下,若是一個線程去更新某個DOM元素,而另外一個線程去刪除這個DOM元素,那麼瀏覽器該執行哪一個操做呢?這就出現衝突了。數組

所以,爲了不復雜的多線陳機制,JavaScript從設計之初就選擇了單線程標準。promise

Event Loop 事件循環

單線程那就表示事件只能一個一個輪着執行,前面沒執行完後面就沒法執行(只能乾等着,這樣也過低效了)。好比,我用ajax向服務端請求一個數據(假設數據量很大,花費時間好久),這時候就會卡在那裏,由於後面的只能等前一個事件把數據成功請求回來再執行接下的代碼,這樣對用戶的體驗就太不友好了。這時候 任務隊列 就出場了。JS中將全部任務分爲同步任務異步任務瀏覽器

目前我的的理解是,只有全部的同步任務執行完纔會去執行異步任務。常見的異步任務有setTimeout函數,http請求,數據庫查詢等。多線程

(1)全部任務都在主線程上執行,造成一個執行棧(execution context stack)。
(2)主線程以外,還存在一個"任務隊列"(task queue)。系統把異步任務放到"任務隊 列"之中,而後繼續執行後續的任務。
(3)一旦"執行棧"( 事件循環隊列)中的全部任務 執行完畢,系統就會讀取"任務隊列"。若是這個時候,異步任務已經結束了等待狀態,就會從"任務隊列"進入執行棧,恢復執行。
(4)主線程不斷重複上面的第三步。

詳情查看此文章閉包

console.log('這裏是調試1');
 setTimeout(() => {
     console.log('這裏是調試2');
 },0)
 console.log('這裏是調試3');
 console.log('這裏是調試4');
 console.log('這裏是調試5');
 // 輸出
 這裏是調試1
 這裏是調試3
 這裏是調試4
 這裏是調試5
 這裏是調試2 // setTimeout中的最後面才輸出來

異步任務之微任務&宏任務

異步任務分爲宏任務(macrotasks)和微任務(microtasks)。常見的宏任務有(setTimeoutsetIntervalsetImmediate),常見的微任務有(Promise.then)。在一個事件循環中,當執行完全部的同步任務後,會在任務隊列中取出異步任務,這時候會優先執行微任務,接着再執行宏任務。換句話說,微任務就是爲了插隊而存在的。看一下下面這個例子:

setTimeout(() => console.log(1));
new Promise((resolve) => {
   resolve();
   console.log(2);
}).then(() => {
   console.log(3);
})
console.log(4);

輸入結果爲:2 - 4 - 3 - 1

setTimeout(...)相關

一、在setTimeout(...)中能夠用async(...)await(...),可是隻能當前setTimeout(...)執行棧中生效;
二、同一層級的多個setTimeout(...)相互獨立,就算setTimeout(...)中有await(...)也不會影響到其餘setTimeout(...)的執行;

阮一峯 JavaScript運行機制詳解
樸靈 標註
mdn

選擇異步仍是同步?

一、在JS中默認的方式是同步的,個別是異步的(http請求,setTimeout)。假設如今有一個場景,咱們須要從後端接口獲取數據(異步任務),而後對該數據進行處理,這時候咱們就須要將獲取數據這一異步任務阻塞,也就是將其轉化爲同步任務。

二、常見的處理異步(將異步變爲同步)的方式按照出現時間排序有:回調函數Promiseasync與await

2、閉包與回調函數

一、不管經過何種手段將 內部函數傳遞到這個函數定義時的 詞法做用域(定義時的詞法做用域指的是函數定義時的那個位置) 之外執行,內部函數都會持有對原始定義做用域的引用,不管在何處執行這個函數都會使用閉包。
二、不管函數在哪裏被調用,也不管它如何被調用,它的詞法做用域都只由函數被聲明時所處的位置決定。
三、閉包使得函數能夠繼續訪問定義時的詞法做用域。
四、只要使用了回調函數,實際上就是在使用閉包。
---《你不知道的JavaScript》

一、通常來講,block(塊級)做用域被執行完後,裏面的數據就被回收了。

二、做用: 將某個塊級做用域裏面的變量能夠被外部使用。

三、詞法做用域:靜態做用域,查找做用域的順序是按照函數定義時的位置來決定的。

四、全局做用域是在V8啓動過程當中就建立了,且一直保存在內存中不會被銷燬的,直到V8退出。而函數做用域是在執行該函數時建立的,當函數執行結束以後,函數做用域就隨之被銷燬了。

咱們來看一個閉包的案例:

function foo() {
    let a = 2;
    function bar() {
        console.log(a);
        a++;
    }
    return bar;
}
let baz = foo();
baz(); // 2
baz(); // 3

函數foo(...)中返回了一個名爲bar的函數,接着 let baz = foo();就將bar(...)這個函數賦值給了baz,因此在必定程度上baz至關於bar(...),接着咱們就執行baz(在bar定義時的詞法做用域之外執行了)(詞法做用域: 函數在哪裏定義,他的詞法做用域就在哪裏);這也就印證了咱們前面說的,將一個內部函數傳遞到這個函數所在的詞法做用域之外執行,因此就產生了閉包,由於咱們在外面獲取到了foo函數內部的變量a,同時這也是閉包產生的效果。

三、總結:

  • 何時產生閉包: 將一個函數傳遞到這個函數所在的詞法做用域之外執行;
  • 閉包的做用: 引用某個塊級做用域裏面變量(持續引用)

四、只要使用了回調函數,實際上就是在使用閉包。下面咱們來看一個案例。

function wait(message) {
    setTimeout(function timer() {
      console.log(message);
    }, 1000);
}
wait('Hello, World');

將一個內部函數(名爲timer)傳遞給setTimeout(...)。timer具備涵蓋wait(...)做用域的閉包,所以還保有對變量message的引用。wait(...)執行1000毫秒後,它的內部做用域並不會消失,timer函數依然保有wait(...)做用域的閉包。由於它能夠引用到message這個變量。

五、下面咱們來看一個稍微複雜點的案例(利用回調函數進行同步傳值):

// 模擬獲取ajax請求的數據(異步)
function getAjaxData(cb) {
    // 用setTimeout實現異步請求
    setTimeout(function() {
      // 假設data是咱們請求獲得的數據 咱們須要將數據發送給別人
      const data = "請求獲得的數據";
      cb(data);
    }, 1000)
}
// 獲取ajax請求的響應數據並對數據進行處理
getAjaxData(function handleData(tempData) {
    tempData = tempData + '666';
    console.log(tempData); // 請求獲得的數據666
});

handleData(...)做爲參數傳進getAjaxData(...)中,所以cb(data)中的data就做爲參數傳進了handleData(...)中,這樣也就達到了傳值的做用了。

六、回調函數存在的問題:信任問題。以上面的例子進行改進。

function getAjaxData (cb) {
  // 用setTimeout實現異步請求
  setTimeout(function () {
    // 假設data是咱們請求獲得的數據 咱們須要將數據發送給別人
    const data = "請求獲得的數據";
    cb(data);
    cb(data);
  }, 1000)
}
// 獲取ajax請求的響應數據並對數據進行處理
getAjaxData(function handleData (tempData) {
  tempData = tempData + '666';
  console.log(tempData); // 請求獲得的數據666
});

假設getAjaxData(...)這個方法是由第三方庫引進來的,咱們並不清楚裏面的代碼邏輯細節,這樣的話handleData(...)的執行就存在不肯定性,好比上面我增長了一個cb(data),這handleData(...)就會執行兩次,固然這不是咱們想要的效果,所以回調的處理就不可控了。

回調最大的問題是控制反轉,它會致使信任鏈的徹底斷裂。
由於回調函數內部的調用狀況是不肯定的,可能不調用,也可能被調用了屢次。
---《你不知道的JavaScript》

Promise的出現正是爲了解決這個問題。

七、備註: 關於js中內存回收的問題

3、Promise

Promise一旦決議( resolve),一直保持其決議結果不變
解決回調調用過早的問題
解決回調調用過晚的問題
解決回調未調用的問題
解決調用次數過少或過多

Promise API概述

一、new Promise(...)構造器

二、Promise.resolve(...)和Promise.reject(...)

三、then(...)和catch(...)

四、Promise.all([...])和Promise.race([...])

Promise源碼解讀

一、new Promise 時,須要傳遞一個executor執行器,執行器馬上執行;

二、executor 接受兩個參數,分別是resolve和reject;

三、promise 只能從 pending 到 rejected,或者從 pending 到 fulfilled;

四、promise 的狀態一旦確認,就不會再改變;

五、promise都有then方法,then接受兩個參數,分別是promise成功的回調 onFulfilled,和promise失敗的回調onRejected;

六、若是調用then時,promise已經成功,則執行onFulfilled,並將promise的值做爲參數傳遞進去。若是promise已經失敗,那麼執行onRejected,並將promise失敗的緣由做爲參數傳遞進去。若是promise的狀態是pending,須要將onFulfilled和onRejected函數存放起來,等待狀態肯定後,再依次將對應的函數執行;

七、then的參數onFulfilled和onRejected能夠缺省;

八、promise能夠then屢次,promise的then方法返回一個promise;

九、若是then返回的是一個結果,那麼就會把這個結果做爲參數,傳遞給下一個then的成功的回調(onFulfilled);

十、若是then中拋出異常,那麼就會把這個異常做爲參數,傳遞給下一個then的失敗的回調(onRejected);

十一、若是then返回的是一個promise,那麼就會等這個promise執行完,promise若是成功,就走下一個then的成功,若是失敗,就走下一個then的失敗;

流程

一、當promise中有異步任務:若是promise中有異步任務的話,那他的status爲pedding,以後全部的then都會放到待辦任務數組裏面。
二、鏈式調用的實現:then方法返回一個promise。
三、當then中有異步任務:兩種狀況:

  • 僅僅是一個異步任務:按照正常狀況來,會再最後面輸出。
  • 異步任務在一個promise中,而且該promise有return:後面的全部then會放進待辦任務數組中,這種狀況會等到return的promise resolve(...)以後,將狀態改變以後纔會再執行後面的(遍歷待辦任務數組),也就是至關於將then(...)中的異步任務阻塞了。

源碼實現&解析

一、實現promise(...)方法,該方法有一個回調函數exectue參數 ;

當咱們new promise((resolve, reject) => {});時,將會執行該回調函數 ;

而且resolve對應到promise(...)中的res(...)

reject對應到promise(...)中的rej(...)

當咱們執行到resolve(...)時,纔會執行res(...)

function promise(exectue) {
  const res = (value) => { }
  const rej = (reason) => { }
  exectue(res, rej);
}

// resolve對應的是promise(...)中的res(...)
// reject對應的是promise(...)中的rej(...)
// 所以只有執行了resolve(...)或reject(...) 才改變status的值
const test = new promise((resolve, reject) => {
  console.log('這裏是調試1');
  setTimeout(() => {
    console.log('這裏是調試2');
    resolve();
  }, 3000)
})

// 這裏是調試1
// 這裏是調試2
// 執行了res

二、promise(...)方法中維護幾個變量,用於存儲執行節點的狀態&數據:

  • fulfilled:標誌任務是否已完成狀態;
  • pedding:標誌任務是否正在進行中狀態(未完成狀態);
  • status:標誌當前執行節點的狀態,節點的初始狀態爲pedding
  • value:存儲promise狀態成功時的值;
  • reason:存儲promise狀態失敗時的值;
  • onFulfilledCallbacks :數組,存儲成功的回調任務;
  • onRejectedCallbacks:數組,存儲失敗的回調任務;
function promise(exectue) {
  this.fulfilled = 'fulfilled';
  this.pedding = 'pedding';
  this.rejected = 'rejected';
  this.status = this.pedding;
  this.value;
  this.reason;
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];
  const res = (value) => {
    if(this.status === this.pedding) {
      // 執行了resolve就要改變status爲fulfilled
      this.status = this.fulfilled;
      this.value = value;
      // 遍歷執行放在待辦任務數組中的事件
      this.onFulfilledCallbacks.forEach(fn => fn());
    }
  }
  const rej = (reason) => {
    if(this.status === this.pedding) {
      // 執行了reject就要改變status爲rejected
      this.status = this.rejected;
      this.reason = reason;
      this.onRejectedCallbacks.forEach(fn => fn());
    }
  }
  exectue(res, rej);
}

三、實現then(...),爲符合鏈式調用,then(...)方法必須返回一個promise(...)

promise.prototype.then = function (onFulfilled, onRejected) {
  // this指向promise(...)對象
  const that = this;
  const promise2 = new promise((resolve, reject) => {
    if(that.status === that.fulfilled) {
      onFulfilled();
      resolve();
    }
    if(that.status === that.pedding) { }
    if(that.status === that.rejected) { }
  })
  return promise2;
}
// 調用
const test = new promise((resolve, reject) => {
  console.log('這裏是調試1');
  resolve();
})
test.then(() => {
  console.log('這裏是調試2');
}).then(() => {
  console.log('這裏是調試3');
})

// 這裏是調試1
// 這裏是調試2
// 這裏是調試3

四、到這裏,咱們已實現了基本的鏈式調用的功能了,那若是在promise(...)中是一個異步事件時,而且咱們須要阻塞這個異步任務(將異步轉化爲同步),預計的效果是隻有執行了resolve(...)才能執行then(...)中的代碼,能夠怎麼實現呢?

promise(...)中是須要阻塞的異步任務時,那麼當執行到then(...)時,此時的statuspedding,須要將鏈式調用的執行節點根據FulfilledRejected兩種狀況分別添加到onFulfilledCallbacksonRejectedCallbacks這兩個變量中,等到異步任務執行完,resolve(...)以後才輪到鏈式調用節點的執行,改進代碼:

promise.prototype.then = function (onFulfilled, onRejected) {
  // this指向promise(...)對象
  const that = this;
  const promise2 = new promise((resolve, reject) => {
    if(that.status === that.fulfilled) {
      onFulfilled();
      resolve();
    }
    if(that.status === that.pedding) {
      console.log('這裏是調試4');
      this.onFulfilledCallbacks.push(() => {
        onFulfilled();
        resolve();
      })
    }
    if(that.status === that.rejected) {
      this.onRejectedCallbacks.push(() => {
        onRejected();
        reject();
      })
    }
  })
  return promise2;
}

// 調用
const test = new promise((resolve, reject) => {
  setTimeout(() => {
    console.log('這裏是調試1');
    resolve();
  }, 1000)
})
test.then(() => {
  console.log('這裏是調試2');
}).then(() => {
  console.log('這裏是調試3');
})

// 這裏是調試4
// 這裏是調試4
// 這裏是調試1
// 這裏是調試2
// 這裏是調試3

五、接下來,這裏又有一個場景,當咱們在then(...)方法中有異步任務,咱們一樣想要讓該異步任務阻塞,又該怎麼弄呢?按照咱們上面的邏輯是阻塞不了then(...)方法中的異步任務的,你們能夠嘗試一下。

const test = new promise((resolve, reject) => {
  setTimeout(() => {
    console.log('這裏是調試1');
    resolve();
  }, 1000)
})
test.then(() => {
  setTimeout(() => {
    console.log('這裏是調試2');
  }, 2000)
}).then(() => {
  console.log('這裏是調試3');
})

// 輸出
// 這裏是調試4
// 這裏是調試4
// 這裏是調試1
// 這裏是調試3
// 這裏是調試2

要想阻塞then(...)中的異步任務,這裏得分紅兩種狀況來討論:一種是要阻塞異步任務,須要在then(...)返回一個promise(...),另外一種是不須要阻塞異步任務,那就不須要返回任何值。繼續改進代碼:

promise.prototype.then = function (onFulfilled, onRejected) {
  // this指向promise(...)對象
  const that = this;
  const promise2 = new promise((resolve, reject) => {
    if(that.status === that.fulfilled) {
        let x = onFulfilled(that.value); 
        if(x && typeof x === 'object' || typeof x === 'function') {
          let then = x.then;
          if(then) {
            then.call(x, resolve, reject);
          } else {
            resolve(x);
          }
    }
    if(that.status === that.pedding) {
      this.onFulfilledCallbacks.push(() => {
        // onFulfilled(...) 爲then(...)方法中的回調函數
        let x = onFulfilled(that.value); // 執行了then(...)方法中的回調函數並將返回值賦給x
        // 判斷then(...)中回調函數返回值類型 是否爲function
        if(x && typeof x === 'object' || typeof x === 'function') {
          let then = x.then;
          // 若是then存在 說明返回了一個promise
          if(then) {
            // 這裏採用的方法是將resolve放在返回的promise的then裏面執行 .
            // 這樣只有當then(...)方法中的異步事件執行完才resolve(...) 很牛皮
            // call(...)綁定this的指向
            then.call(x, resolve, reject);
          }
        } else {
          resolve(x);
        }
      })
    }
    if(that.status === that.rejected) {
      this.onRejectedCallbacks.push(() => {  
        let x = onRejected(that.reason);
        if(x && typeof x === 'object' || typeof x === 'function') {
          let then = x.then;
          if(then) {
            then.call(x, resolve, reject);
          } else {
            resolve(x);
          }
      })
    }
  })
  return promise2;
}


const test = new promise((resolve, reject) => {
  setTimeout(() => {
    console.log('這裏是調試1');
    resolve();
  }, 1000)
})
test.then(() => {
  // 若要阻塞then(...)中的異步任務須要return一個promise
  return new promise((resolve1, reject1) => {
    setTimeout(() => {
      console.log('這裏是調試2');
      resolve1();
    }, 2000)
  })
}).then(() => {
  console.log('這裏是調試3');
})

// 這裏是調試1
// 這裏是調試2
// 這裏是調試3

六、到這裏,關於promise核心的內容基本已經實現了,再看一下如下案例:

new promise(function(resolve){
    console.log('這裏是調試1');
    resolve();
}).then(function(){
    console.log('這裏是調試2');
});
console.log('這裏是調試3');

// 這裏是調試1
// 這裏是調試2
// 這裏是調試3
new Promise(function(resolve){
    console.log('這裏是調試1');
    resolve();
}).then(function(){
    console.log('這裏是調試2');
});
console.log('這裏是調試3');

// 這裏是調試1
// 這裏是調試3
// 這裏是調試2

能夠發現,咱們的promise和原生的Promise輸出結果不同。根據Promise規範,在.then(...)方法中執行的程序屬於微任務(異步任務分爲微任務宏任務,通常的異步事件屬於宏任務,微任務比宏任務先執行,但咱們本身實現的promise並不支持這個規則,原生的Promise才支持),所以須要將其轉化爲異步任務,下面咱們用setTimeout(...)來模擬實現一下異步任務(原生Promise 並非用setTimeout(...)實現的)

promise.prototype.then = function (onFulfilled, onRejected) {
  const that = this;
  const promise2 = new promise((resolve, reject) => {
    if (that.status === that.fulfilled) {
      setTimeout(() => {
        let x = onFulfilled(that.value);
        if (x && typeof x === 'object' || typeof x === 'function') {
          let then = x.then;
          if (then) {
            then.call(x, resolve, reject);
          }
        } else {
          resolve(x);
        }
      })
    }
    if (that.status === that.pedding) {
      that.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          let x = onFulfilled(that.value);
          if (x && typeof x === 'object' || typeof x === 'function') {
            let then = x.then;
            if (then) {
              then.call(x, resolve, reject);
            }
          } else {
            resolve(x);
          }
        })
      })
      that.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          let x = onRejected(that.reason);
          if (x && typeof x === 'object' || typeof x === 'function') {
            let then = x.then;
            if (then) {
              then.call(x, resolve, reject);
            }
          } else {
            resolve(x);
          }
        })
      })
    }
    if (that.status === that.rejected) {
      setTimeout(() => {
        let x = onRejected(that.reason);
        if (x && typeof x === 'object' || typeof x === 'function') {
          let then = x.then;
          if (then) {
            then.call(x, resolve, reject);
          }
        } else {
          resolve(x);
        }
      })
    }
  })
  return promise2;
}

// 這裏是調試1
// 這裏是調試3
// 這裏是調試2

七、再來看一個案例:

const test = new promise((res, rej) => {
  setTimeout(() => {
    console.log('這裏是調試1');
    res();
  }, 4000);
})
// 回調函數push進test.onFulfilledCallbacks數組中
test.then(() => {
  return new promise((res1) => {
    setTimeout(() => {
      console.log('這裏是調試2');
      res1();
    }, 3000);
  })
})
// 回調函數也是push進test.onFulfilledCallbacks數組中,所以兩個then(...)是相互獨立的,執行順序按照常規的來處理
test.then(() => {
  console.log('這裏是調試3');
})

// 輸出
// 這裏是調試1
// 這裏是調試3
// 這裏是調試2

源碼實現參考
宏任務微任務參考

4、async與await

  • async、await相對於promise多了等待的效果
  • 原理: Promise + 生成器 = async/await;
  • async函數必定會返回一個Promise對象。若是一個async函數的返回值看起來不是promise,那麼它將會被隱式得包裝在一個promise中;
  • 重要: 使用async與await時,await後面的函數必須返回決議的Promise,否則,程序將中止在await這一步;
  • async、await相對與promise多了等待的效果;具體是怎麼實現的?--- 使用了yield暫停函數,只有等前面的promise決議以後才執行next(...),程序才繼續日後面執行,表現出來的就是暫停程序的效果;

迭代器

一、一種特殊對象,用於爲迭代過程設計的專有接口,簡單來講就相似與遍歷一個對象;

二、調用返回一個結果對象,對象中有next()方法;

三、next()方法返回一個對象: value: 表示下一個要返回的值;done:沒有值能夠返回時爲true,不然爲false;

四、迭代器簡易內部實現:

function createIterator(items) {
  let i = 0;
  return {
    next: function() {
      let done = (i >= items.length);
      // items[i++] 至關於 items[i]; i++;
      let value = !done ? items[i++] : undefined;
      return {
        value: value,
        done: done // true表示後面沒有值了
      };
    }
  };
}
// 只建立了一個實例 所以iterator一直是同一個
let iterator = createIterator([1,2,3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

生成器

一、一個返回迭代器的函數;實現了一種順序、看似同步的異步流程控制表達風格;
二、關鍵字yield:表示函數暫停運行;
三、過程:每當執行完一條yield語句後函數就暫停了,直到再次調用函數的next()方法纔會繼續執行後面的語句;
四、使用yield關鍵字能夠返回任何值或表達式( 表示要傳遞給下一個過程的值);
五、注意點:

  • yield關鍵字只能在生成器函數的直接內部使用(在生成器函數內部的函數中使用會報錯);
  • 不能用箭頭函數來建立生成器;

簡易使用示例:

function *createInterator() {
  yield 1;
  yield 2;
  yield 3;
}
// 生成器的調用方式與普通函數相同,只不過返回一個迭代器
let iterator = createInterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

迭代消息傳遞:

function *foo(x) {
  const y = x * (yield);
  return y;
}
const it = foo(6);
// 啓動foo(...)
it.next(); 
const res = it.next(7); // next(...)的數量老是比yield的多一個
console.log(res); // 42

首先,傳入6做爲參數。而後調用it.next(...),這會啓動*foo(...)

接着,在*foo(...)內部,開始執行語句const y = x ...,但隨後就遇到了一個yield表達式。它就會在這一點上暫停*foo(...)(在賦值語句中間),並不是在本質上要求調用代碼爲yield表達式提供一個結果值。接下來,調用it.next(7),這一句把值7傳回做爲被暫停的yield表達式結果。

因此,這是賦值語句實際上就是const y = 6 * 7。如今,return y返回值42做爲調用it.next(7)的結果。

注意,根據你的視角不一樣,yieldnext(...)調用有一個不匹配。通常來講,須要的next(...)調用要比yield語句多一個,前面的代碼片斷就有一個yield和兩個next(...)調用。

由於第一個next(...)老是用來啓動生成器,並運行到第一個yield處。不過,是第二個next(...)調用完成第一個被暫停的yield表達式,第三個next(...)完成第二個yield,以此類推。

消息是雙向傳遞的:

yield(...)做爲一個表達式,能夠發出消息響應next(...)

next(...)也能夠向暫停的yield表達式發送值。注意:前面咱們說到,第一個next(...)老是用來啓動生成器的,此時沒有暫停的yield來接受這樣一個值,規範和瀏覽器都會默認丟棄傳遞給第一個next(...)的任何東西。所以,啓動生成器必定要用不帶參數的next(...);

function *foo(x) {
  const y = x * (yield "Hello");
  return y;
}
const it = foo(6);
let res = it.next(); // 第一個next(),並不傳入任何東西
console.log(res.value); // Hello
res = it.next(7); // 向等待的yield傳入7
console.log(res.value); // 42

源碼實現&解析

async&await是基於生成器+Promise封裝的語法糖,使用async修飾的function實際上會被轉化爲一個生成器函數,並返回一個決議的Promise(...)await對應到的是yield,所以await只能在被async修飾的方法中使用

下面看一個例子:這是一個常見的使用async/await的案例

// 模擬多個異步狀況
function foo1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(100); // 若是沒有決議(resolve)就不會運行下一個
    }, 3000)
  })
}
function foo2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(200);
    }, 2500)
  })
}
async function test() {
  const f1 = await foo1();
  const f2 = await foo2();
  console.log('這裏是調試1');
  console.log(f1);
  console.log(f2);
  return 'haha';
}
test().then((data) => {
  console.log('這裏是調試2');
  console.log(data);
});
// 這裏是調試1
// 100
// 200
// 這裏是調試2
// haha

接着咱們來看一下如何將這個案例中的async/await生成器+Promise來實現:

test(...)轉化爲生成器函數:

function *test() {
  const f1 = yield foo1();
  const f2 = yield foo2();
  console.log('這裏是調試1');
  console.log(f1);
  console.log(f2);
  return 'haha';
}

實現方法run(...),用於調用生成器*test(...)

function run(generatorFunc) {
  return function() {
    const gen = generatorFunc();
    // async 默認返回Promise
    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let generatorResult;
        try {
          // 至關於執行gen.next(...)
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }
        // gen.next() 獲得的結果是一個 { value, done } 的結構
        const { value, done } = generatorResult;
        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(val => step('next', val), err => step('throw', err));
        }
      }
      step("next");
    })
  }
}

const haha = run(test);
haha().then((data) => {
  console.log('這裏是調試2');
  console.log(data);
});

// 這裏是調試1
// 100
// 200
// 這裏是調試2
// haha

async/await 參考

相關文章
相關標籤/搜索