# 為什么用 Promise
調用 API 如下載文件,讀取文件一些平時你會執行的異步操作而不使用同步操作。因為服務器可能會性能下降,響應慢等等問題,你不希望因為等待著結果,讓整個進程都被堵住。比如下面模擬遠程求兩個數之和:
~~~
// 遠程相加兩數字,調用 API 獲得結果
?
function addAsync (num1, num2, callback) {
? ?//使用有名的 jQuery getJSON 的回調 API
? ?return $.get('http://www.example.com', {
? ? ? ?num1: num1,
? ? ? ?num2: num2
? }, callback);
}
?
addAsync(1, 2, function(data) {
? ?const result = data; // 這里你得到 result = 3
? ?console.log(result);
});
~~~
這個語法看上去 OK,但想一系列的異步操作怎么辦?比如說,不同于一次僅僅相加兩個數字,我們希望加 3 次,即下次運算用到上次計算的結果。
~~~
let resultA, resultB, resultC;
?
function addAsync (num1, num2, callback) {
? ?return $.get('http://www.example.com', {
? ? ? ?num1: num1,
? ? ? ?num2: num2
? }, callback);
}
?
addAsync(1, 2, function(data) {
? ?// callback 1
? ?resultA = data; // you get result = 3 here
?
? ?addAsync(resultA, 3, function(data1) {
? ? ? ?// callback 2
? ? ? ?resultB = data1; // you get result = 6 here
?
? ? ? ?addAsync(resultB, 4, function(data2) {
? ? ? ? ? ?// callback 3
? ? ? ? ? ?resultC = data2; // you get result = 10 here
? ? ? ? ? ?console.log('total' + resultC);
? ? ? ? ? ?console.log(resultA, resultB, resultC);
? ? ? });
? });
});
~~~
這個語法很不友好。更貼切地說,這個看上去像金字塔,人們經常稱呼為 "回調地獄",因為一個回調嵌在另一個回調之中。若你還覺得沒有問題,可以想象有 10 個回調,這代碼得嵌套 10 次!
# Promise
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。說直白點就是 Promise 就是一種寫代碼的方式,并且是用來寫 JavaScript 編程中的異步代碼的。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。
有了 Promise 對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise 對象提供統一的接口,使得控制異步操作更加容易。
一個 Promise 對象 有以下幾種狀態:
* pending: 初始狀態,既不是成功,也不是失敗狀態。
* fulfilled: 意味著操作成功完成。
* rejected: 意味著操作失敗。
!\[\](file://D:/H5%E5%AD%A6%E7%A7%91%E5%8D%81%E4%B9%9D%E6%9C%9F%E8%AF%BE%E7%A8%8B/nodejs-prosime07/img/promise%E7%8A%B6%E6%80%81%E5%8F%98%E5%8C%96.png?lastModify=1570810183)
# 基本用法
構造實例:
* 構造函數接受一個函數作為參數;
* 調用構造函數得到實例 p 的同時,作為參數的函數會立即執行;
* 參數函數接受兩個回調函數參數 resolve 和 reject;
* 在參數函數被執行的過程中,**若在其內部調用 resolve,會將 p 的狀態變成 fulfilled,或者調用 reject,會將 p 的狀態變成 rejected**。
調用 then:
* 調用 then 可以為實例 p 注冊兩種狀態回調函數;
* 當實例 p 的狀態為 fulfilled,會觸發第一個函數執行;
* 當實例 p 的狀態為 rejected,則觸發第二個函數執行。
調用 catch:
* 調用 catch 用于注冊 rejected 狀態的回調函數,同時該回調也是程序出錯的回調,即如果前面的程序運行過程中出錯,也會進入執行該回調函數。
~~~
var isMomHappy = false;
// Promise
var p = new Promise(
? ?function (resolve, reject) {
? ? ? ?if (isMomHappy) {
? ? ? ? ? ?var phone = {
? ? ? ? ? ? ? ?brand: 'Samsung',
? ? ? ? ? ? ? ?color: 'black'
? ? ? ? ? };
? ? ? ? ? ?resolve(phone); // 完成了
? ? ? } else {
? ? ? ? ? ?var reason = new Error('媽媽不開心');
? ? ? ? ? ?reject(reason); // reject
? ? ? }
? }
);
?
p.then(function (fulfilled) {
? ? ? ?// 太好啦, 你獲得了一個新手機
? ? ? ?console.log(fulfilled);
? ? ? ?// output: { brand: 'Samsung', color: 'black' }
? })
? .catch(function (error) {
? ? ? ?// 好不幸,你媽媽沒買手機
? ? ? ?console.log(error.message);
? ? ? ?// output: '媽媽不開心'
? });
~~~
# 使用 Promise 改造 AJAX
~~~
let request = obj => {
? ?return new Promise((resolve, reject) => {
? ? ? ?let xhr = new XMLHttpRequest();
? ? ? ?xhr.open(obj.method || "GET", obj.url);
? ? ? ?if (obj.headers) {
? ? ? ? ? ?Object.keys(obj.headers).forEach(key => {
? ? ? ? ? ? ? ?xhr.setRequestHeader(key, obj.headers[key]);
? ? ? ? ? });
? ? ? }
? ? ? ?xhr.onload = () => {
? ? ? ? ? ?if (xhr.status >= 200 && xhr.status < 300) {
? ? ? ? ? ? ? ?resolve(xhr.response);
? ? ? ? ? } else {
? ? ? ? ? ? ? ?reject(xhr.statusText);
? ? ? ? ? }
? ? ? };
? ? ? ?xhr.onerror = () => reject(xhr.statusText);
? ? ? ?xhr.send(obj.body);
? });
};
?
request({url: "employees.json"})
? .then(data => {
? ? ? ?console.log(data);
? })
? .catch(error => {
? ? ? ?console.log(error);
? });
~~~