async原理解析

前言

async其實就是 Generator的語法糖,看本篇文章以前能夠先看一下上一篇文章 Generator函數。理解 Generator就容易理解爲何說async是異步編程的完美解決方案了。前端

目錄

  • async函數
  • async函數原理
  • 常見的關於async的筆試題

async函數

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error){
          return reject(error);
        } 
        resolve(data);
    });
  });
};

const foo = function* () {
  const f1 = yield readFile('/src/lib');
  const f2 = yield readFile('/src/utils');

  console.log(f1.toString());
  console.log(f2.toString());
};
複製代碼

把上面代碼的Generator函數 foo 能夠寫成 async 函數,就是這樣:git

const asyncReadFile = async function () {
  const f1 =  await readFile('/src/lib');
  const f2 =  await readFile('/src/utils');

  console.log(f1.toString());
  console.log(f2.toString());
};
複製代碼

能夠發現,async函數就是將Generator函數的星號(*)替換成async,將 yield替換成 await,僅此而已。github

async函數是基於 Generator的改進,體如今如下4點ajax

  1. 內置執行器。 Generator函數的執行必須靠執行器。因此纔有了 Thunk函數和co模塊,而 async函數自帶執行器。async函數的執行和普通函數同樣。
asyncReadFile();
複製代碼
  1. 更好的語義。 asyncawait,比起星號和yield,語義更清楚了。 async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。編程

  2. 更廣的適應性。 即便 Generator函數能夠藉助co模塊自動執行,可是co模塊後面只能是Thunk函數或Promise對象,而async函數的await命令後面,能夠是 Promise對象和原始類型的值(數值、字符串和布爾值,但這是會自動轉成當即 resolvedPromise對象微信

  3. 返回值是 Promise。 aysnc函數返回值爲 Promise,這比Generator函數的返回值是Iterator對象方便多了。markdown

async函數徹底能夠看做多個異步操做,包裝成的一個Promise 對象,而await命令就是內部then命令的語法糖。異步

總之就是 Generator 函數雖然是JS借鑑其餘語言,根據JS自己單線程的特色實現的協程,可是使用起來會麻煩不少,每次都要本身去寫執行器,而 async函數就是爲了解決這些重複的工做而生的。其實 async函數就是將Generaor函數和自動執行器完美地封裝在了一塊兒。async

async函數的實現原理

就是將Generator函數和自動執行器,包裝在一個函數裏。異步編程

async function fn(args) {
    // ...
}
function fn(args) {
    return spawn(function* () {
        // ...
    })
}
複製代碼

全部的 async 函數均可以寫成上面的第二種形式,其中 spawn 函數就是自動執行器。

// 接受 Generator 函數做爲參數,返回一個 Promise 對象
function spawn(genF) {
  return new Promise(function(resolve, reject) {
    // 執行genF,獲得一個內部指針對象
    const gen = genF();

    function step(nextF) {
      let next;

      // 進行錯誤處理
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }

      // 將 next.value 轉成 Promise對象
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }

    // 會反覆調用 step
    step(function() { return gen.next(undefined); });

  });
}
複製代碼

能夠看到,實際上是 Generator函數和Promise的組合,實現了一個自動執行器,返回 Promise對象

常見的關於async的筆試題

  • 實現一個sleep
  • 實現一個紅綠燈: 紅燈2秒,黃燈1秒,綠燈3秒
  • 使用 async 實現Promise.all()的效果

實現一個 sleep

每隔1秒輸出 1, 2, 3, 4, 5

function sleep(interval) {
    return new Promise(resolve => {
        setTimeout(resolve, interval);
    })
}

// 用法
async function one2FiveInAsync() {
    for (let i = 1; i <= 5; i++) {
        console.log(i);
        await sleep(1000);
    }
}
one2FiveInAsync();

複製代碼

實現一個紅綠燈

紅燈2秒,黃燈1秒,綠燈3秒

function sleep(duration) {
    return new Promise(resolve => {
        setTimeout(resolve, duration);
    })
}
async function changeColor(color, duration) {
    console.log('當前顏色', color);
    await sleep(duration);
}
async function main() {
    await changeColor('紅色', 2000);
    await changeColor('黃色', 1000);
    await changeColor('綠色', 3000);
}
main();
複製代碼

使用 async 實現 Promise.all()的效果

假設 getFoogetBar是兩個用於發起ajax請求的函數。

// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();

let foo = await fooPromise;
let bar = await barPromise;
複製代碼

上面兩種寫法,getFoo 和 getBar 都是同時觸發,這樣就會縮短程序的執行時間。

上面只是簡單示例,思考一下,寫出完整代碼。

總結

  • async 函數原理就是 Generator函數 和 自動執行器包裝了一下。
  • Generator就是能夠暫定執行和在以前停下的位置接着執行。好比發送一個接口請求,發出以後,JS能夠去幹其餘的事兒,接口請求回來以後(數據經過next傳入),會接着繼續執行。可是它不能自動執行,因此須要自動執行器, thunk函數和co模塊都是,可是async給咱們封裝得更加完美。

關於

文章首發於 個人github博客

最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。

牧碼的星星
相關文章
相關標籤/搜索