ES6 Promise

ES6 Promise 目前各家瀏覽器的支援程度雖然還沒到可以直接用的程度,但是目前已經有非常多的 polyfill,差不多是可以開始使用的時候了。

如果習慣了 jQuery 設計的 Deferred 物件,應該會對於 ES6 的 Promise 設計很不習慣吧,相較於 jQuery 是產生好物件然後提供一些 method 做操作,要不要封裝起來也是開發者的事,ES6 Promise 的設計更是強調了封裝的特性,要怎麼 resolve、reject,在一開始就要決定好了,只有在建構函數裡面碰的到 resolve 和 reject 的觸發點:

new Promise(function (resolve, reject) {
  //...
});

這樣的設計雖然在物件封裝上比較嚴謹,但是其實會讓一些程式碼多了一層的縮排,例如本來用 jQuery Deferred 包起來的 XHR:

function JQXHR(url) {
  var xhr = new XMLHttpRequest();
  var dfd = new $.Deferred();
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) { dfd.resolve(xhr.response); }
  }
  xhr.open('GET', url);
  xhr.send();
  return dfd.promise();
}

用 ES6 Promise 就要改寫成:

function ESXHR(url) {
  var xhr = new XMLHttpRequest();
  var dfd = new Promise(function (resolve) {
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) { resolve(xhr.response); }
    }
  });
  xhr.open('GET', url);
  xhr.send();
  return dfd;
}

第二個差異就在於回傳的 thenable 物件要怎麼把結果改掉,以前 jQuery 的時候可以在 callback 裡面回傳新的 Deferred 物件改結果:

dfd.then(null, function () {
  return $.Deferred().resolve();
});

沒特別傳 Deferred 物件的話不管用then串接幾次的話結果都不會改變,不過 ES6 Promise 就不一樣了,不管是 resolve 狀況還是 reject 狀況,then回傳的預設就是一個新的 resolved 狀態的 Promise 物件。那要怎樣改變狀態呢?這裡就要用throw new Error()了。在 ES6 Promise 的then裡面,不管是 resolve 還是 reject 的 handler,都是回傳任意值會讓後面拿到新的 resolved 的 Promise 物件,而如果在執行中 throw error 出去,就會讓後面拿到 rejected 的 Promise 物件:

dfd.then(function (val) {
  if (val === 0) {
    throw new Error('');
  }
});

然後就是 rejected 狀態的 Promise 也是有值的,如果是 throw error 產生的,那就是看 throw 什麼東西,那個東西就會變成新的 Promise 物件的值,而以往手動 throw error 時,為了相容性都會產生 Error 物件,在這邊就不必如此了,其實可以隨便傳想要給後面使用的值。

再來,有時候只想要處理 reject 的狀況,會寫成:

dfd.then(null, function () {
  //rejected handler
});

有個 null 放前面其實蠻討厭的,不過 ES6 有提供一個catch可以用:

dfd.catch(function () {
  //rejected handler
});

這個以前倒是沒有類似的東西,蠻方便的。大概瞭解到這樣就可以使用 ES6 Promise 了,另外還有Promise.allPromise.race可以做和jQuery.when類似的事情(race 是新的控制),至於我為什麼說現在差不多可以開始使用呢,除了 Polyfill 齊全外,其實還有一個原因是一些新的網路標準也開始使用 ES6 Promise 了,像是 Web Crypto,看 spec 比較不明顯,不過看 MDN 的介紹,就有列出 encrypt、decrypt、sign 等花時間的 method 回傳都是 Promise 物件,實做就可以丟到背景的 Worker 去處理,才不會把 UI 卡住。而除了 Web Crypto 外,還有像是 ES7 可能會有的 await 語法也是要接 Promise 物件,總之是個未來趨勢,我覺得現在比較尷尬的是在 ES6 Promise 出來前的一堆標準都是用 event base 設計的,像是 File Reader 之類的,不知道有沒有機會慢慢都改到 Promise。