JavaScript:體驗異步的優雅解決方案

1、異步解決方案的進化史

JavaScript的異步操做一直是個麻煩事,因此不斷有人提出它的各類解決方案。能夠追溯到最先的回調函數(ajax老朋友),到Promise(不算新的朋友),再到ES6的Generator(強勁的朋友)。
幾年前咱們可能用過一個比較著名的Async.js,可是它沒有擺脫回調函數,而且錯誤處理也是按照「回調函數的第一個參數用來傳遞錯誤」這樣一個約定。而衆所周知的回調地獄仍然是一個比較突出的問題,直到Generator改變了這種異步風格。
可是ES7的async await的出現(碉堡的新朋友),咱們能夠輕鬆寫出同步風格的代碼同時又擁有異步機制,能夠說是目前最簡單,最優雅,最佳的解決方案了。ajax

2、async await語法

async await語法比較簡單,能夠認爲是Generator的語法糖,比起星號和yield更具備語義化。下面一個簡單的例子表示1秒以後輸出hello world:併發

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

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

asyncPrint('hello world', 1000);
  1. await只能用在async函數中,若是用在普通函數就會報錯
  2. await後面跟的是一個Promise對象(固然其它值也能夠,可是會包裝成一個當即resolve的Promise,也就沒有意義了)
  3. await會等待Promise的結果返回再繼續執行

await等待的雖然是Promise對象,可是沒必要寫.then(),直接能夠獲得返回值,將上面的代碼微調,發現返回值result也是能夠輸出hello world:異步

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(_ => {resolve('hello world')}, ms);
  });
}

async function asyncPrint(ms) {
  let result = await timeout(ms);
  console.log(result)
}

asyncPrint(1000);

3、async await錯誤處理

前面說了await等待的雖然是Promise對象,可是沒必要寫.then(),因此其實也不用寫.catch()了,直接用try catch就能捕捉錯誤,這樣能夠避免錯誤處理代碼很是冗餘和笨重,仍是將上面的例子微調:async

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(_ => {reject('error')}, ms);//reject模擬出錯,返回error
  });
}

async function asyncPrint(ms) {
  try {
     console.log('start');
     await timeout(ms);//這裏返回了錯誤
     console.log('end');//因此這句代碼不會被執行了
  } catch(err) {
     console.log(err); //這裏捕捉到錯誤error
  }
}

asyncPrint(1000);

若是有多個await,能夠一塊兒放在try catch中:函數

async function main() {
  try {
    const async1 = await firstAsync();
    const async2 = await secondAsync();
    const async3 = await thirdAsync();
  }
  catch (err) {
    console.error(err);
  }
}

4、async await注意點

1). 前面已經說過,await命令後面的Promise對象,運行結果極可能是reject或邏輯報錯,因此最好把await放在try catch代碼塊中。
2). 多個await命令的異步操做,若是不存在依賴關係,讓它們同時觸發。post

const async1 = await firstAsync();
const async2 = await secondAsync();

上面代碼中,async1和async2若是是兩個獨立的異步操做,這樣寫會比較耗時,由於只有firstAsync完成之後,纔會執行secondAsync,徹底能夠用Promise.all優雅地處理:code

let [async1, async2] = await Promise.all([firstAsync(), secondAsync()]);

3). await只能用在async函數之中,若是用在普通函數就會報錯:對象

async function main() {
  let docs = [{}, {}, {}];

  //報錯 await is only valid in async function
  docs.forEach(function (doc) {
    await post(doc);
    console.log('main');
  });
}
function post(){
  return new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

在forEach內部方法加上async就能夠了:ip

async function main() {
  let docs = [{}, {}, {}];

  docs.forEach(async function (doc) {
    await post(doc);
    console.log('main');
  });
}
function post(){
  return new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

可是你會發現3個main是同時輸出的,這就說明post是併發執行的,而不是繼發執行,改爲for就能夠解決問題,3個main是分別相隔1秒輸出:回調函數

async function main() {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await post(doc);
    console.log('main');
  }
}
function post(){
  return new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

總之,用了async await以後整我的神清氣爽,能夠用很是簡潔和優雅的代碼實現各類花式異步操做,而且在業務邏輯複雜的狀況下能夠不用陷入回調地獄中。不敢說這必定是終極的解決方案,但確實是目前最優雅的解決方案!

相關文章
相關標籤/搜索