Promise 簡介

weixin_34068198發表於2016-06-25

譯者注: 到處是回撥函式,程式碼非常臃腫難看, Promise 主要用來解決這種程式設計方式, 將某些程式碼封裝於內部。

Promise 直譯為“承諾”,但一般直接稱為 Promise;

程式碼的可讀性非常重要,因為開發人員支出一般比計算機硬體的支出要大很多倍。

雖然同步程式碼更容易跟蹤和除錯, 但非同步方式卻具有更好的效能與靈活性.
怎樣在同一時刻發起多個請求, 然後分別處理響應結果? Promise 現已成為 JavaScript 中非常重要的一個組成部分, 很多新的API都以 promise 的方式來實現。下面簡要介紹 promise, 以及相應的 API 和使用示例!

Promises 周邊

XMLHttpRequest 是非同步API, 但不算 Promise 方式。當前使用 Promise 的原生 api 包括:

Promise 會越來越流行,所以前端開發需要快速掌握它們。當然, Node.js 是另一個使用 Promise 的平臺(顯然, Promise 在Node中是一個核心特性)。

測試 promises 可能比你想象的還要容易, 因為 setTimeout 可以用來當作非同步“任務”!

Promise 基本用法

直接使用 new Promise() 建構函式的方式, 應該只用來處理遺留的非同步任務程式設計, 例如 setTimeout 或者 XMLHttpRequest。 通過 new 關鍵字建立一個新的 Promise 物件, 該物件有 resolve(搞定!) 和 reject(拒絕!) 兩個回撥函式:

var p = new Promise(function(resolve, reject) {
    // ... ... 
    // 此處,可以執行某些非同步任務,然後...
    // 在回撥中,或者任何地方執行 resolve/reject

    if(/* good condition */) {
        resolve('傳入成果結果資訊,如 data');
    }
    else {
        reject('失敗:原因...!');
    }
});

p.then(function(data) { 
    /* do something with the result */
}).catch(function(err) {
    /* error :( */
});

一般是由開發人員根據非同步任務執行的結果,來手動呼叫 resolve 或者 reject. 一個典型的例子是將 XMLHttpRequest 轉換為基於Promise的任務:

// 本段示例程式碼來源於 Jake Archibald's Promises and Back:
// http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest

function get(url) {
  // 返回一個 promise 物件.
  return new Promise(function(resolve, reject) {
    // 執行常規的 XHR 請求
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
    // Resolve the promise with the response text
    resolve(req.response);
      }
      else {
    // Otherwise reject with the status text
    // which will hopefully be a meaningful error
    reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("網路出錯"));
    };

    // Make the request
    req.send();
  });
};

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

有時候在 promise 方法體中不需要執行非同步任務 —— 當然,在有可能會執行非同步任務的情況下, 返回 promise 將是最好的方式, 這樣只需要給定結果處理函式就行。在這種情況下, 不需要使用 new 關鍵字, 直接返回 Promise.resolve() 或者 Promise.reject()即可。例如:

var userCache = {};

function getUserDetail(username) {
  // In both cases, cached or not, a promise will be returned

  if (userCache[username]) {
    // Return a promise without the "new" keyword
    return Promise.resolve(userCache[username]);
  }

  // Use the fetch API to get the information
  // fetch returns a promise
  return fetch('users/' + username + '.json')
    .then(function(result) {
      userCache[username] = result;
      return result;
    })
    .catch(function() {
      throw new Error('Could not find user: ' + username);
    });
};

因為總是會返回 promise, 所以只需要通過 thencatch 方法處理結果即可!

then

每個 promise 例項都有 then 方法, 用來處理執行結果。 第一個 then 方法回撥的引數, 就是 resolve() 傳入的那個值:

new Promise(function(resolve, reject) {
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(result) {
    console.log(result);
});

// console 輸出的結果:
// 10

then 回撥由 promise 的 resolved 觸發。你也可以使用鏈式的 then` 回撥方法:

new Promise(function(resolve, reject) { 
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(num) { console.log('first then: ', num); return num * 2; })
.then(function(num) { console.log('second then: ', num); return num * 2; })
.then(function(num) { console.log('last then: ', num);});

// console 輸出的結果:
// first then:  10
// second then:  20
// last then:  40

每個 then 收到的結果都是之前那個 then 返回的值。

如果 promise 已經 resolved, 但之後才呼叫 then 方法, 則立即觸發回撥。如果promise被拒絕之後才呼叫 then, 則回撥函式不會被觸發。

catch

當 promise 被拒絕時, catch 回撥就會被執行:

new Promise(function(resolve, reject) {
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { reject('Done!'); }, 3000);
})
.then(function(e) { console.log('done', e); })
.catch(function(e) { console.log('catch: ', e); });

// console 輸出的結果:
// 'catch: Done!'

傳入 reject 方法的引數由你自己決定。一般來說是傳入一個 Error 物件:

reject(Error('Data could not be found'));

Promise.all

想想JavaScript載入器的情形: 有時候會觸發多個非同步互動, 但只在所有請求完成之後才會做出響應。—— 這種情況可以使用 Promise.all 來處理。Promise.all 方法接受一個 promise 陣列, 在所有 promises 都搞定之後, 觸發一個回撥:

Promise.all([promise1, promise2]).then(function(results) {
    // Both promises resolved
})
.catch(function(error) {
    // One or more promises was rejected
});

Promise.all 的最佳示例是通過fetch同時發起多個 AJAX請求時:

var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');

Promise.all([request1, request2]).then(function(results) {
    // Both promises done!
});

你也可以組合使用 fetch 和 Battery 之類的 API ,因為他們都返回 promises:

Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) {
    // Both promises done!
});

當然, 處理拒絕的情況比較複雜。如果某個 promise 被拒絕, 則 catch 將會被第一個拒絕(rejection)所觸發:

var req1 = new Promise(function(resolve, reject) { 
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { resolve('First!'); }, 4000);
});
var req2 = new Promise(function(resolve, reject) { 
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.all([req1, req2]).then(function(results) {
    console.log('Then: ', one);
}).catch(function(err) {
    console.log('Catch: ', err);
});

// From the console:
// Catch: Second!

隨著越來越多的 API 支援 promise, Promise.all 將會變得超級有用。

Promise.race

Promise.race 是一個有趣的函式. 與 Promise.all 相反, 只要某個 priomise 被 resolved 或者 rejected, 就會觸發 Promise.race:

var req1 = new Promise(function(resolve, reject) { 
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { resolve('First!'); }, 8000);
});
var req2 = new Promise(function(resolve, reject) { 
    // 通過 setTimeout 模擬非同步任務
    setTimeout(function() { resolve('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
    console.log('Then: ', one);
}).catch(function(one, two) {
    console.log('Catch: ', one);
});

// From the console:
// Then: Second!

一個案例是請求的資源有 主站資源和備用資源(以防某個不可用)。

改變習慣, 使用 Promise

在過去幾年中 Promise 一直是個熱門話題(如果你是 Dojo Toolkit 使用者,那麼就是已經有10年了), 已經從一個JavaScript框架變成了語言的一個主要成分. 很快你就會看到大多數新的 JavaScript api 都會基於 Promise 的方式來實現……

… 當然這是一件好事! 開發人員能夠避開回撥的地獄, 非同步互動也可以像其他變數一樣傳遞. Promise 還需要一段時間來普及, 現在是時候去學習他們了!

本文最初釋出於: http://zcfy.cc/article/351

翻譯人員: 鐵錨 http://blog.csdn.net/renfufei

翻譯時間: 2016年6月25日

原文時間: 2015年11月2日

原文連結: https://davidwalsh.name/promises

相關文章