關於promise(一)

該新特性屬於 ECMAScript 2015ES6)規範,在使用時請注意瀏覽器兼容性。node

因爲ES6原生提供Promise,因此無需安裝Promise庫。但在ES5環境下咱們可使用bluebird庫來提供Promise。web

背景知識:數據庫

  理解同樣東西,固然要先了解它是怎麼來的json

  JavaScript是單線程的,這意味着代碼是按順序執行的。對於瀏覽器而言,JavaScript代碼和其餘任務共享一個線程,不一樣的瀏覽器略有差別,但大致上這些和JavaScript共享線程的任務主要包括重繪、更新樣式、用戶交互等,全部這些任務操做都會阻塞其餘任務。segmentfault

  避免事件阻塞的經常使用方法是使用事件監聽器。咱們能夠爲某些特定事件設置監聽器,若是事件發生的話,便馬上觸發監聽器,你應該已經習慣使用回調函數來解決這個問題了,例如:數組

var img1 = document.querySelector('.img-1');
img1.addEventListener('load', function() {
  // 圖片加載完成
});
img1.addEventListener('error', function() {
  // 出問題了
});

  上面的代碼中,咱們添加了兩個監聽器,請求圖片,回調函數只在事件發生的時候纔會被觸發。可是經過事件機制還存在幾個問題:promise

    事件在綁定以前就發生了怎麼辦?瀏覽器

    在添加監聽器以前,圖片加載發生了錯誤怎麼辦?網絡

  僅僅是一張圖片就存在這麼多問題,那麼若是有一堆圖片要處理,又該怎麼辦?一個愈來愈流行的異步解決方案Promise來拯救。異步

再如:

  異步方法調用中,每每會出現回調函數一環扣一環的狀況。這種狀況致使了回調金字塔問題的出現。不只代碼寫起來費勁又不美觀,並且問題複雜的時候,閱讀代碼的人也難以理解。

舉例以下:

db.save(data, function(data){
    // do something...
    db.save(data1, function(data){
        // do something...
        db.save(data2, function(data){
            // do something...
            done(data3); // 返回數據
        })
    });
});

  假設有一個數據庫保存操做,一次請求須要在三個表中保存三次數據。那麼咱們的代碼就跟上面的代碼類似了。這時候假設在第二個db.save出了問題怎麼辦?基於這個考慮,咱們又須要在每一層回調中使用相似try...catch這樣的邏輯。這個就是萬惡的來源,也是node剛開始廣爲詬病的一點。

  另一個缺點就是,假設咱們的三次保存之間並無先後依賴關係,咱們仍然須要等待前面的函數執行完畢, 才能執行下一步,而沒法三個保存並行,以後返回一個三個保存事後須要的結果。(或者說實現起來須要技巧)

Promise

  JavaScript的一大特色就是會涉及到大量的異步代碼。同步代碼一般易於理解和調試,而異步代碼則具備更好的性能和靈活性。

  目前Promise正逐漸稱爲JavaScript世界的一個重要組成部分,而且不少新的API也都基於Promise進行了實現。

  目前已經有一些原生API使用了Promise,包括:

    · Battery API

    · Fetch API

    · ServiceWorker API

  所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。是ES6規範新增的對象,它能夠用於延遲計算和異步計算。

  一個Promise對象表明着一個還未完成,但預期會完成的操做。須要記住:

    · 一個Promise要麼成功要麼失敗,而且狀態不可變
    · 能夠根據Promise的結果設置特定的回調函數

語法:

new Promise(executor);

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

  new Promise()構造器應該只被用於傳統的異步任務上,例如setTimeoutXMLHttpRequest

  經過new關鍵字建立一個新的Promise,它接收一個回調函數做爲參數,這個函數在建立Promise對象的時候會當即獲得執行,該回調函數又包括了兩個特定的回調函數,分別被命名爲resolvereject,成功後調用resolve,失敗則調用reject。這個函數一般被用來執行一些異步操做,操做完成之後能夠選擇調用成功回調函數(resolve)來觸發promise的成功狀態,或者,在出現錯誤的時候調用失敗回調函數(reject)來觸發promise的失敗。

  根據不一樣的任務,由開發者來決定resolvereject在函數體內的位置。

描述

  Promise 對象是一個返回值的代理,這個返回值在promise對象建立時未必已知。它容許你爲異步操做的成功返回值或失敗信息指定處理方法。 這使得異步方法能夠像同步方法那樣返回值:異步方法會返回一個包含了原返回值的 promise 對象來替代原返回值。

  Promise對象有如下幾種狀態:

    pending: 初始狀態, 既不是 fulfilled 也不是 rejected.

    fulfilled: 成功的操做.

    rejected: 失敗的操做.

  狀態轉換關係爲:pending->fulfilledpending->rejected

  隨着狀態的轉換將觸發各類事件(如執行成功事件、執行失敗事件等)。

  pending狀態的promise對象既可轉換爲帶着一個成功值的fulfilled 狀態,也可變爲帶着一個失敗信息的 rejected 狀態。當狀態發生轉換時,promise.then綁定的方法(函數句柄)就會被調用。(當綁定方法時,若是 promise對象已經處於 fulfilled rejected 狀態,那麼相應的方法將會被馬上調用, 因此在異步操做的完成狀況和它的綁定方法之間不存在競爭條件。)

  注意:若是一個promise對象處在fulfilledrejected狀態而不是pending狀態,那麼它也能夠被稱爲settled狀態。你可能也會聽到一個術語resolved ,它表示promise對象處於settled狀態,或者promise對象被鎖定在了調用鏈中。關於promise的狀態, Domenic Denicola States and fates 有更多詳情可供參考。

  由於Promise.prototype.thenPromise.prototype.catch方法返回 promises對象, 因此它們能夠被鏈式調用—— 一種被稱爲 composition 的操做。

  

示例:

var promise = new Promise(function func(resolve, reject){
    // do somthing, maybe async
    if (success){
      return resolve(data);
    } else {
      return reject(data);
    }
});

promise.then(function(data){
    // do something... e.g
    console.log(data);
}, function(err){
    // deal the err.
})

  promise對象在建立的時候會執行func函數中的邏輯。

  邏輯處理完畢而且沒有錯誤時,resolve這個回調會將值傳遞到一個特殊的地方。這個特殊的地方在哪呢?就是下面代碼中的then,咱們使用then中的回調函數來處理resolve後的結果。好比上面的代碼中,咱們將值簡單的輸出到控制檯。若是有錯誤,則rejectthen的第二個回調函數中,對錯誤進行處理。

  使用Promise則很是的簡單,能夠調用Promise對象的then()方法來處理異步計算的結果。then接收兩個回調函數,分別是成功的回調函數和失敗時的回調函數,這兩個參數都是可選的。

  Promise的使用有兩點須要記住的:

    · then()方法能夠鏈式調用
    · catch()方法能夠做爲錯誤處理語句的語法糖,至關於then(undefined, function(error) { ... });

 

再來看一個例子:用於將XMLHttpRequest轉換爲一個基於Promise的接口。

  咱們以GET請求爲例:

function get(url) {
  // 返回一個新的 Promise
  return new Promise(function(resolve, reject) {
    // 經典 XHR 操做
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // 當發生 404 等情況的時候調用此函數
      // 因此先檢查狀態碼
      if (req.status == 200) {
        // 以響應文本爲結果,完成此 Promise
        resolve(req.response);
      }
      else {
        // 不然就以狀態碼爲結果否認掉此 Promise
        // (提供一個有意義的 Error 對象)
        reject(Error(req.statusText));
      }
    }; 

    // 網絡異常的處理方法
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // 發出請求
    req.send();
  });
}

  咱們如今能夠這麼調用它:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

  如今咱們發起XHR請求便變得簡單直觀的多了。story.json文件的內容以下:

{
  "heading": "A story about something",
  "chapterUrls": [
    "chapter-1.json",
    "chapter-2.json",
    "chapter-3.json",
    "chapter-4.json",
    "chapter-5.json"
  ]
}

鏈式調用

  上面咱們說過then()接收兩個參數,分別對應成功和失敗時的回調函數。咱們還能夠將多個then方法串聯起來,用於修改結果或執行更多的異步操做。

  promisethen方法依然可以返回一個Promise對象,這樣咱們就又能用下一個then來作同樣的處理。

  第一個then中的兩個回調函數決定第一個then返回的是一個什麼樣的Promise對象:

    · 假設第一個then的第一個回調沒有返回一個Promise對象,那麼第二個then的調用者仍是原來的Promise對象,只不過其resolve的值變成了第一個then中第一個回調函數的返回值。
    · 假設第一個then的第一個回調函數返回了一個Promise對象,那麼第二個then的調用者變成了這個新的Promise對象,第二個then等待這個新的Promise對象resolve或者reject以後執行回調。

  你能夠對結果進行修改,而後返回一個新的值,例如:

new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() { resolve(10); }, 3000);
})

.then(num => { console.log('first then: ', num); return num * 2; })
.then(num => { console.log('second then: ', num); return num * 2; })
.then(num => { console.log('last then: ', num);});

// From the console:
// first then:  10
// second then:  20
// last then:  40

  每一個then接收前一個then的返回值的結果。

  回到以前的get函數,咱們能夠修改返回值的類型,將結果進行必定的轉換:

get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
});

爲了讓代碼變得更簡單,能夠再次進行改進:

  由於JSON.parse只接收一個參數,並返回轉換後的結果,咱們能夠直接使用then(JSON.parse)

  then中的回調函數,咱們能夠直接使用ES6的胖箭頭函數,這樣可讓代碼更直觀

get('story.json').then(JSON.parse).then(response => console.log("JSON data: ", response);

  因爲這段代碼會被重複調用,咱們能夠定義一個新的getJSON函數:

function getJSON(url) {
  return get(url).then(JSON.parse);  // 返回一個獲取JSON並加以解析的Promise
}

  對於串聯起來的then()方法而言:若是你返回了一個值,那麼它就會被傳給下一個then()的回調。

  若是你返回一個「類Promise」對象,則下一個then()就會等待這個Promise明確結束(成功/失敗)纔會執行。

getJSON('story.json')
  .then(story => getJSON(story.chapterUrls[0]))
  .then(chapter => console.log("Got chapter 1!, " chapter));

  在上面的代碼中,咱們首先發起對story.json的異步請求,它會返回給咱們一個URL列表,而後咱們請求其中的第一個。

錯誤處理

  前面咱們已經知道,then接收兩個參數,一個處理成功時的回調函數,一個處理失敗時的回調函數。

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
});

  你還可使用catch來進行錯誤處理,實際上,它不過是then(undefined, func)的語法糖而已。這樣可以讓代碼更直觀:

get('story.json')
  .then(response => console.log('Success!', response))
  .catch(error => console.error('Failed!', error));

promise還有它的一些屬性和方法:

屬性

Promise.length:長度屬性,其值爲 1 (構造器參數的數目).

Promise.prototype:表示 Promise 構造器的原型.

方法

Promise.all(iterable)

  這個方法返回一個新的promise對象,該promise對象在iterable裏全部的promise對象都成功的時候纔會觸發成功,一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗。這個新的promise對象在觸發成功狀態之後,會把一個包含iterable裏全部promise返回值的數組做爲成功回調的返回值,順序跟iterable的順序保持一致;若是這個新的promise對象觸發了失敗狀態,它會把iterable裏第一個觸發失敗的promise對象的錯誤信息做爲它的失敗錯誤信息。Promise.all方法常被用於處理多個promise對象的狀態集合。(能夠參考jQuery.when方法---譯者注)

Promise.race(iterable)

  當iterable參數裏的任意一個子promise被成功或失敗後,父promise立刻也會用子promise的成功返回值或失敗詳情做爲參數調用父promise綁定的相應句柄,並返回該promise對象。

Promise.reject(reason)

  調用Promiserejected句柄,並返回這個Promise對象。

Promise.resolve(value)

  用成功值value完成一個Promise對象。若是該value爲可繼續的(thenable,即帶有then方法),返回的Promise對象會「跟隨」這個value,採用這個value的最終狀態;不然的話返回值會用這個value知足(fullfil)返回的Promise對象。

 

 

 

 

 

參考:

developer mozilla

JavaScript Promise API

淺談ES6原生Promise 

Promise讓代碼更優雅

Promise系列文章

Promise實戰

相關文章
相關標籤/搜索