async/await 原理及簡單實現

解決函數回調經歷了幾個階段, Promise 對象, Generator 函數到async函數。async函數目前是解決函數回調的最佳方案。不少語言目前都實現了async,包括Python ,java spring,go等。javascript

async await 的用法

async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。java

function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}
const func = async ()=>{
	const f1 = await getNum(1)
  const f2 = await getNum(f1)
  console.log(f2) 
  // 輸出3 
}
func()
複製代碼

async /await 須要在function外部書寫async,在內部須要等待執行的函數前書寫await便可spring

深刻理解

理解async函數須要先理解Generator函數,由於async函數是Generator函數的語法糖chrome

Generator[ˈdʒɛnəˌretɚ]函數-生成器

Generator是ES6標準引入的新的數據類型。Generator能夠理解爲一個狀態機,內部封裝了不少狀態,同時返回一個迭代器Iterator對象。能夠經過這個迭代器遍歷相關的值及狀態。 Generator的顯著特色是能夠屢次返回,每次的返回值做爲迭代器的一部分保存下來,能夠被咱們顯式調用。promise

Generator函數的聲明

通常的函數使用function聲明,return做爲回調(沒有遇到return,在結尾調用return undefined),只能夠回調一次。而Generator函數使用function*定義,除了return,還使用yield返回屢次。瀏覽器

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.next();  // {value: 3, done: true}
result.next();  //{value: undefined, done: true}
複製代碼

在chrome瀏覽器中這個例子裏,咱們能夠看到,在執行foo函數後返回了一個 Generator函數的實例。它具備狀態值suspended和closed,suspended表明暫停,closed則爲結束。可是這個狀態是沒法捕獲的,咱們只能經過Generator函數的提供的方法獲取當前的狀態。 在執行next方法後,順序執行了yield的返回值。返回值有value和done兩個狀態。value爲返回值,能夠是任意類型。done的狀態爲false和true,true即爲執行完畢。在執行完畢後再次調用返回{value: undefined, done: true} 注意:在遇到return的時候,全部剩下的yield再也不執行,直接返回{ value: undefined, done: true }異步

Generator函數的方法

Generator函數提供了3個方法,next/return/throwasync

next方式是按步執行,每次返回一個值,同時也能夠每次傳入新的值做爲計算函數

function* foo(x) {
    let a = yield x + 1;
    let b= yield a + 2;
    return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(1);  // {value: 1, done: false}
result.next(2);  // {value: 4, done: false}
result.next(3);  // {value: 3, done: true}
result.next(4);  //{value: undefined, done: true}
複製代碼

return則直接跳過全部步驟,直接返回 {value: undefined, done: true}ui

throw則根據函數中書寫try catch返回catch中的內容,若是沒有寫try,則直接拋出異常

function* foo(x) {
  try{
    yield x+1
    yield x+2
    yield x+3
    yield x+4
    
  }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.throw();  // catch it {value: undefined, done: true}
result.next();  //{value: undefined, done: true}
複製代碼

這裏能夠看到在執行throw以前,順序的執行了狀態,可是在遇到throw的時候,則直接走進catche並改變了狀態。

這裏還要注意一下,由於狀態機是根據執行狀態的步驟而執行,因此若是執行thow的時候,沒有遇到try catch則會直接拋錯 如下面兩個爲例

function* foo(x) {
    yield x+1
  try{
    yield x+2
   }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.throw();  // catch it {value: undefined, done: true}
result.next();  //{value: undefined, done: true}
複製代碼

這個例子與以前的執行狀態同樣,由於在執行到throw的時候,已經執行到try語句,因此能夠執行,而下面的例子則不同

function* foo(x) {
    yield x+1
  try{
    yield x+2
   }catch(e){
    console.log('catch it')
  }
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.throw();  // Uncaught undefined
result.next();  //{value: undefined, done: true}
複製代碼

執行throw的時候,尚未進入到try語句,因此直接拋錯,拋出undefined爲throw未傳參數,若是傳入參數則顯示爲傳入的參數。此狀態與未寫try的拋錯狀態一致。

遍歷

Generator函數的返回值是一個帶有狀態的Generator實例。它能夠被for of 調用,進行遍歷,且只可被for of 調用。此時將返回他的全部狀態

function* foo(x) {
   console.log('start')
    yield x+1
   console.log('state 1')
    yield x+2
   console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
	console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}
複製代碼

調用for of方法後,在後臺調用next(),當done屬性爲true的時候,循環退出。所以Generator函數的實例將順序執行一遍,再次調用時,狀態爲已完成

狀態的存儲和改變

Generator函數中yield返回的值是能夠被變量存儲和改變的。

function* foo(x) {
    let a = yield x + 0;
    let b= yield a + 2;
    yield x;
    yield a 
    yield b
}
const result = foo(0)
result.next() // {value: 0, done: false}
result.next(2) // {value: 4, done: false}
result.next(3) // {value: 0, done: false}
result.next(4) // {value: 2, done: false}
result.next(5) // {value: 3, done: false}
複製代碼

以上的執行結果中,咱們能夠看到,在第二步的時候,咱們傳入2這個參數,foo函數中的a的變量的值0被替換爲2,而且在第4次迭代的時候,返回的是2。而第三次迭代的時候,傳入的3參數,替換了b的值4,並在第5次迭代的時候返回了3。因此傳入的參數,是替代上一次迭代的生成值。

yield* 委託

在Generator函數中,咱們有時須要將多個迭代器的值合在一塊兒,咱們可使用yield *的形式,將執行委託給另一個Generator函數

function* foo1() {
    yield 1;
    yield 2;
    return "foo1 end";
}

function* foo2() {
    yield 3;
    yield 4;
}

function* foo() {
    yield* foo1();
    yield* foo2();
	  yield 5;
}

const result = foo();

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: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"
複製代碼

foo在執行的時候,首先委託給了foo1,等foo1執行完畢,再委託給foo2。可是咱們發現,」foo1 end」 這一句並無輸出。 在整個Generator中,return只能有一次,在委託的時候,全部的yield*都是以函數表達式的形式出現。return的值是表達式的結果,在委託結束以前其內部都是暫停的,等待到表達式的結果的時候,將結果直接返回給foo。此時foo內部沒有接收的變量,因此未打印。 若是咱們但願捕獲這個值,可使用yield *foo()的方式進行獲取。

實現一個簡單的async/await

如上,咱們掌握了Generator函數的使用方法。async/await語法糖就是使用Generator函數+自動執行器來運做的。 咱們能夠參考如下例子

// 定義了一個promise,用來模擬異步請求,做用是傳入參數++
function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}

//自動執行器,若是一個Generator函數沒有執行完,則遞歸調用
function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

// 所須要執行的Generator函數,內部的數據在執行完成一步的promise以後,再調用下一步
var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);
複製代碼

在執行的過程當中,判斷一個函數的promise是否完成,若是已經完成,將結果傳入下一個函數,繼續重複此步驟。

總結

async/await很是好理解,基本理解了Generator函數以後,幾句話就能夠描述清楚。這裏沒有過多的繼續闡述Generator函數的內部執行邏輯及原理,若是有對此有深刻理解的童鞋,歡迎補充說明。

相關文章
相關標籤/搜索