ES6 Promise 和 Async/await的使用

huangjincq發表於2017-12-22

你可能知道,Javascript語言的執行環境是"單執行緒"(single thread)。

所謂"單執行緒",就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。

這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript程式碼長時間執行(比如死迴圈),導致整個頁面卡在這個地方,其他任務無法執行。

為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和非同步(Asynchronous)。

1.回撥

回撥是非同步程式設計最基本的方法。

假定有兩個函式f1和f2,後者等待前者的執行結果。

f1();
f2();
複製程式碼

如果f1是一個很耗時的任務,可以考慮改寫f1,把f2寫成f1的回撥函式。

function f1(callback){
  setTimeout(function () {
    // f1的任務程式碼
    callback();
  }, 1000);
}
複製程式碼

執行程式碼就變成下面這樣

f1(f2);
複製程式碼

採用這種方式,我們把同步操作變成了非同步操作,f1不會堵塞程式執行,相當於先執行程式的主要邏輯,將耗時的操作推遲執行。 回撥函式的優點是簡單、容易理解和部署,缺點是不利於程式碼的閱讀和維護,各個部分之間高度耦合,流程會很混亂,而且每個任務只能指定一個回撥函式。

2.Promise

Promises物件是CommonJS工作組提出的一種規範,目的是為非同步程式設計提供統一介面。

簡單說,它的思想是, 每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。 Promises的出現大大改善了非同步變成的困境,避免出現回撥地獄,巢狀層級得到改善。

基本Api

  1. Promise.resolve()
  2. Promise.reject()
  3. Promise.prototype.then()
  4. Promise.prototype.catch()
  5. Promise.all() // 所有的完成
  6. Promise.race() // 競速,完成一個即可

具體api的介紹請看 阮一峰 大神的 ECMAScript 6 入門 在這我舉幾個簡單的場景的實現

模擬兩個非同步請求

為了使程式碼簡介,promise的rejected狀態的相關reject()和catch()方法省略

  // 1請求
  function getData1 () {
    return new Promise(function (resolve, reject) {
      setTimeout(() => {
        console.log('1執行了')
        resolve('請求到模擬資料1111拉')
      }, 2000)
    })
  }
  // 2請求
  function getData2 (params) {
    return new Promise(function (resolve, reject) {
      setTimeout(() => {
        console.log('2執行了')
        resolve('請求到模擬資料22222拉!params:' + params)
      }, 1500)
    })
  }
複製程式碼

promise 實現非同步回撥 非同步列隊

1請求完成後,把1的響應引數傳入2,在發2請求

  function promiseDemo () {
    getData1()
      .then(res => {
        return getData2(res)
      })
      .then(res => {
        console.log(res)
      })
  }
  promiseDemo()
  // 1執行了
  // 2執行了
  // 請求到模擬資料22222拉!params:請求到模擬資料1111拉   用時 3500 ms
複製程式碼

promise.all() 實現非同步回撥 併發 所有的完成

1請求、2請求同時發,兩條響應都收到後在執行

  function promiseDemo () {
    Promise.all([getData1(), getData2()]).then(function (res) {
      console.log(res)
    })
  }
  // 2執行了
  // 1執行了
  // ["請求到模擬資料1111拉", "請求到模擬資料22222拉!params:undefined"]   用時 2000 ms
複製程式碼

promise.race() 實現非同步回撥 併發 競速

1請求、2請求同時發,其中一條收到請求就執行

  function promiseDemo () {
    Promise.race([getData1(), getData2()]).then(function (res) {
      console.log(res)
    })
  }
  // 2執行了
  // 請求到模擬資料22222拉!params:undefined    用時 1500 ms
  // 1執行了   
複製程式碼

由此Promise物件還是很好用的,對於非同步的流程的控制得到了大大改善,通過.then()的方法可進行鏈式呼叫。 可是 .then() .catch() 的使用也導致程式碼非常難看,巢狀也很深,所以async/await就出來了

Async/await

Async/await 是Javascript編寫非同步程式的新方法。以往的非同步方法無外乎回撥函式和Promise。但是Async/await建立於Promise之上。

如何使用 Async 函式

我們還是來看一 看阮一峰 大神的 ECMAScript 6 入門 的例子

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);
複製程式碼

上面程式碼指定50毫秒以後,輸出hello world。 進一步說,async函式完全可以看作多個非同步操作,包裝成的一個 Promise 物件,而await命令就是內部then命令的語法糖

我們看具體的示例

async 實現非同步回撥 非同步列隊

1請求完成後,把1的響應引數傳入2,在發2請求

上文中的promise 實現方法是通過then的鏈式呼叫,但是採用async會更加簡潔明瞭

  async function asyncDemo () {
    const r1 = await getData1()
    const r2 = await getData2(r1)
    console.log(r2)
  }
  // 1執行了
  // 2執行了
  // 請求到模擬資料22222拉!params:請求到模擬資料1111拉   用時 3500 ms
複製程式碼

用同步的書寫方式實現了非同步的程式碼。等待getData1的非同步函式執行完了後發返回值賦值給r1,傳入r2,在執行r2

async 非同步回撥 併發

1請求、2請求同時發,規定請求到達的順序

假如我們有一種這樣的業務需求,併發兩個請求,但是要規定收到請求的順序應該怎麼做的?這裡還是借鑑阮一峰大神的程式碼

  async function asyncDemo2 () {
    const arr = [getData1, getData2]
    const textPromises = arr.map(async function (doc) {
      const response = await doc()
      return response
    })
    // 按次序輸出
    for (const textPromise of textPromises) {
      console.log(await textPromise);
    }
  }
  // 2執行了            (因為2是 1500ms後執行) 所以2先執行
  // 1執行了
  // 請求到模擬資料1拉  (for .. of )規定了輸出的順序
  // 請求到模擬資料22222拉!params:undefined
複製程式碼

面程式碼中,雖然map方法的引數是async函式,但它是併發執行的,因為只有async函式內部是繼發執行,外部不受影響。後面的for..of迴圈內部使用了await,因此實現了按順序輸出

怎麼樣這麼BT的需求都能實現把。

async 總結

他使得非同步程式碼變的不再明顯也是一點弊端咯,不過根據實際情況選擇最合適的非同步程式設計才是最好的選擇。async 是 Generator 函式的語法糖。所以想更深入的理解其中內部原理的趕緊去看看 Generator 函式把

相關文章