1. 程式人生 > >JavaScript非同步處理

JavaScript非同步處理

構建一個應用程式總是會面對非同步呼叫,不論是在 Web 前端介面,還是 Node.js 服務端都是如此,JavaScript 裡面處理非同步呼叫一直是非常噁心的一件事情。以前只能通過回撥函式,後來漸漸又演化出來很多方案,最後 Promise 以簡單、易用、相容性好取勝,但是仍然有非常多的問題。其實 JavaScript 一直想在語言層面徹底解決這個問題,在 ES6 中就已經支援原生的 Promise,還引入了 Generator 函式,終於在 ES7 中決定支援 async 和 await。

基本語法

async/await 究竟是怎麼解決非同步呼叫的寫法呢?簡單來說,就是將非同步操作用同步的寫法來寫。先來看下最基本的語法(ES7 程式碼片段):

const f = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123);
    }, 2000);
  });
};

const testAsync = async () => {
  const t = await f();
  console.log(t);
};

testAsync();

首先定義了一個函式 f ,這個函式返回一個 Promise,並且會延時 2 秒, resolve 並且傳入值 123。 testAsync

 函式在定義時使用了關鍵字 async ,然後函式體中配合使用了 await ,最後執行 testAsync 。整個程式會在 2 秒後輸出 123,也就是說 testAsync 中常量 t 取得了 f中 resolve 的值,並且通過 await 阻塞了後面程式碼的執行,直到 f 這個非同步函式執行完。

對比 Promise

僅僅是一個簡單的呼叫,就已經能夠看出來 async/await 的強大,寫碼時可以非常優雅地處理非同步函式,徹底告別回撥惡夢和無數的 then 方法。我們再來看下與 Promise 的對比,同樣的程式碼,如果完全使用 Promise 會有什麼問題呢?

const f = ()
=>
{ return new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 2000); }); }; const testAsync = () => { f().then((t) => { console.log(t); }); }; testAsync();

從程式碼片段中不難看出 Promise 沒有解決好的事情,比如要有很多的 then 方法,整塊程式碼會充滿 Promise 的方法,而不是業務邏輯本身,而且每一個 then 方法內部是一個獨立的作用域,要是想共享資料,就要將部分資料暴露在最外層,在 then 內部賦值一次。雖然如此,Promise 對於非同步操作的封裝還是非常不錯的,所以 async/await 是基於 Promise 的, await 後面是要接收一個 Promise 例項。

對比 RxJS

RxJS 也是非常有意思的東西,用來處理非同步操作,它更能處理基於流的資料操作。舉個例子,比如在 Angular2 中 http 請求返回的就是一個 RxJS 構造的 Observable Object,我們就可以這樣做:

$http.get(url)
  .map(function(value) {
    return value + 1;
  })
  .filter(function(value) {
    return value !== null;
  })
  .forEach(function(value) {
    console.log(value);
  })
  .subscribe(function(value) {
    console.log('do something.');
  }, function(err) {
    console.log(err);
  });

如果是 ES6 程式碼可以進一步簡潔:

$http.get(url) 
  .map(value => value + 1) 
  .filter(value => value !== null) 
  .forEach(value => console.log(value)) 
  .subscribe((value) => { 
    console.log('do something.'); 
  }, (err) => { 
    console.log(err); 
  });

可以看出 RxJS 對於這類資料可以做一種類似流式的處理,也是非常優雅,而且 RxJS 強大之處在於你還可以對資料做取消、監聽、節流等等的操作,這裡不一一舉例了,感興趣的話可以去看下 RxJS 的 API。

這裡要說明一下的就是 RxJS 和 async/await 一起用也是可以的,Observable Object 中有 toPromise 方法,可以返回一個 Promise Object,同樣可以結合 await 使用。當然你也可以只使用 async/await 配合 underscore 或者其他庫,也能實現很優雅的效果。總之,RxJS 與 async/await 不衝突。

異常處理

通過使用 async/await,我們就可以配合 try/catch 來捕獲非同步操作過程中的問題,包括 Promise 中 reject 的資料。

const f = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(234);
    }, 2000);
  });
};

const testAsync = async () => {
  try {
    const t = await f();
    console.log(t);
  } catch (err) {
    console.log(err);
  }
};

testAsync();

程式碼片段中將 f 方法中的 resolve 改為 reject ,在 testAsync 中,通過 catch 可以捕獲到 reject 的資料,輸出 err 的值為 234。 try/catch 使用時也要注意範圍和層級。如果 try 範圍內包含多個 await ,那麼 catch 會返回第一個 reject 的值或錯誤。

const f1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(111);
    }, 2000);
  });
};

const f2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(222);
    }, 3000);
  });
};

const testAsync = async () => {
  try {
    const t1 = await f1();
    console.log(t1);
    const t2 = await f2();
    console.log(t2);
  } catch (err) {
    console.log(err);
  }
};

testAsync();

如程式碼片段所示, testAsync 函式體中 try 有兩個 await 函式,而且都分別 reject ,那麼 catch 中僅會觸發 f1 的 reject ,輸出的 err 值是 111。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------