1. 程式人生 > >Promise API 簡介

Promise API 簡介

方法回調 sdn mark 編程方式 話題 net == post resolve

Promise API 簡介

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

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

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

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

Promises 周邊

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

  • Battery API
  • fetch API (用來替代 XHR)
  • ServiceWorker 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://www.zcfy.cc/article/351
原文:https://davidwalsh.name/promises

Promise API 簡介