Promise一些小總結

在前端開發中,有個很熟悉的詞叫作「回調」,在處理一些異步的函數的時候,回調被普遍應用,可是大量用回調來編程,會出現嵌套層級過多,代碼風格不規範,不清晰的問題。「Promise/A+規範」是一種很方便的異步編程方式。前端


使用promise進行編程有哪些好處?

  • 將複雜的異步處理輕鬆地進行模式化
  • 代碼更清晰
  • 異常處理更方便
  • 代碼鏈式操做,

先來一段簡單的promise的代碼:ajax

var promise = getAsyncPromise("fileA.txt"); 
promise.then(function(result){
// 獲取文件內容成功時的處理
}).catch(function(error){
// 獲取文件內容失敗時的處理
});
複製代碼

經過這個代碼,是否是以爲promise異步編程很清晰?編程

下面我詳細介紹下promise實用的方法。json


Promise 簡介

1. 構建promise對象

  • Promise相似於 XMLHttpRequest ,從構造函數 Promise 來建立一個新建新 promise 對 象做爲接口。
  • 要想建立一個promise對象、可使用 new 來調用 Promise 的構造器來進行實例化。以下:
var promise = new Promise(function(resolve, reject) { 
// 異步處理
// 處理結束後、調用resolve 或 reject
});
複製代碼

2. 經常使用的方法

  • 對經過new生成的promise對象爲了設置其值在 resolve(成功) / reject(失敗)時調用的回調函數 可使用 promise.then() 實例方法。
promise.then(onFulfilled, onRejected)

//resolve(成功)時 onFulfilled 會被調用
//reject(失敗)時onRejected 會被調用
//onFulfilled 、 onRejected 兩個都爲可選參數。
複製代碼
  • promise.then成功和失敗時均可以使用。另外在只想對異常進行處理時能夠採用promise.then(undefined, onRejected),這種方式,只指定reject時的回調函數便可。 不過這種狀況下 promise.catch(onRejected) 應該是個更好的選擇。
promise.catch(onRejected)
複製代碼

3. 靜態方法

  • 包括 Promise.all() 還有 Promise.resolve() 等在內,主要都是一些對Promise進行操做的 輔助方法。

4. 來個栗子

function asyncFunction() {
    return new Promise(function (resolve, reject) { //①
    setTimeout(function () {
        resolve('Async Hello world'); }, 16); //②
    }); 
}
asyncFunction().then(function (value) { console.log(value); // => 'Async Hello world'
}).catch(function (error) { console.log(error);
});
複製代碼

栗子說明:數組

  1. new Promise構造器以後,會返回一個promise對象
  2. 爲promise對象用設置 .then 調用返回值時的回調函數。
  3. 這個函數會返回promise對象,對於這個promise對象,咱們調用的 方法來設置resolve後的回調函數,catch方法來設置發生錯誤時的回調函數。
  4. 在這種狀況下catch的回調函數並不會被執行(由於promise返回了resolve), 不過若是運行環境沒有提供setTimeout函數的話,那麼上面代碼在執行中就會產生異常,在catch中設置的回調函數就會被執行。

上面的代碼也也能夠不用catch方法,用then(resolve, reject)的形式(鏈式操做時最好用catch,能夠統一捕捉異常):promise

asyncFunction().then(function (value) { console.log(value);
}, function (error) { console.log(error);
});
複製代碼

Promise 狀態詳解

大體的瞭解promise處理流程,單獨介紹一下promise狀態bash

1. 大體三個狀態

  • resolve(成功)時。此時會調用 onFulfilled
  • reject(失敗)時。此時會調用 onRejected
  • 既不是resolve也不是reject的狀態。也就是promise對象剛被建立後的初始化狀態等

此處輸入圖片的描述

*注意:從Pending轉換爲Fulfilled或Rejected以後, 這個promise對象的狀 態就不會再發生任何變化。異步

當promise的對象狀態發生變化時,用 .then 來定義只會被調用一次的函數。async

編寫Promise代碼:

1. 建立promise對象

  1. new Promise(fn) 返回一個promise對象
  2. 在 fn 中指定異步等處理 • 處理結果正常的話,調用 resolve(處理結果值) • 處理結果錯誤的話,調用 reject(Error對象)

來個XHR的promise對象的栗子

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest();  //栗子就不作ie兼容了
        req.open('GET', URL, true); 
        req.onload = function () {
            if (req.status === 200) { 
                resolve(req.responseText);
            } 
            else {
                reject(new Error(req.statusText));
            } 
        };
        req.onerror = function () { 
            reject(new Error(req.statusText));
        };
        req.send(); 
    });
}
// 運行示例
var URL = "http://httpbin.org/get"; 
getURL(URL).then(function onFulfilled(value) {
console.log(value); }).catch(function onRejected(error) {
console.error(error); });

複製代碼

栗子說明:異步編程

  1. getURL 只有在經過XHR取得結果狀態爲200時纔會調用 resolve - 也就是隻有數據取 得成功時,而其餘狀況(取得失敗)時則會調用 reject 方法。
  2. resolve(req.responseText) 在response的內容中加入了參數。 resolve方法的參數並無特 別的規則,基本上把要傳給回調函數參數放進去就能夠了。 ( then 方法能夠接收到這 個參數值)

6. 編寫promise對象處理方法

讓咱們在實際中使用一下剛纔建立的返回promise對象的函數

getURL("http://example.com/"); // => 返回promise對象
複製代碼

爲promise對象添加處理方法主要有如下兩種:

  • promise對象被 resolve 時的處理(onFulfilled)
  • promise對象被 reject時的處理(onRejected)
getURL(URL).then(onFulfilled, onRejected); //純then寫法
getURL(URL).then( onFulfilled).catch( onRejected);  // then chatch寫法
複製代碼

注:通常說來,使用 .catch 來將resolve和reject處理分開來寫是比較推薦的作法, 這二者的 區別會在then和catch的區別中再作詳細介紹。


詳解promise

1. 使用 Promise.resolve

  • Promise.resolve(value) 能夠認爲是 new Promise() 方法的快捷方式。
//好比 Promise.resolve(42); 能夠認爲是如下代碼的語法糖。
new Promise(function(resolve){ 
    resolve(42);
});
複製代碼

在這段代碼中的 resolve(42);會讓這個promise對象當即進入肯定(即resolved)狀態,並將 42 傳遞給後面then裏所指定的 onFulfilled 函數。

Promise.resolve(42).then(
    function (value) {
        console.log(value)  //42
    }
);
複製代碼

:Promise.resolve做爲newPromise()的快捷方式,在進行promise對象的初始化或者編寫 測試代碼的時候都很是方便。


  • Promise.resolve方法另外一個做用就是將thenable對象轉換爲promise對象。

    **thenable對象**:簡單來講它就是一個很是相似promise的東西(*就像咱們有時稱具備.length方法的非數組對象爲Arraylike同樣,thenable指的是一個具備.then 方法的對象*。)   
    複製代碼
    • 最簡單的例子就是jQuery.ajax(),它的返回值就是thenable的(由於 jQuery.ajax()的返回值是jqXHRObject對象,這個對象具備 .then 方法)

    • 用Promise.resolve來轉換爲一個promise對象。變成了promse對象的話,就能直接使用 then 或者 catch等這些在ES6Promises裏定 義的方法了。

var promise = Promise.resolve(
    $.ajax('/json/comment.json')
) // => promise對象 

promise.then(function(value){
    console.log(value); 
});

複製代碼

總結:簡單總結一下 Promise.resolve 方法的話,能夠認爲它的做用就是將傳遞給它的參數填 充(Fulfilled)到promise對象後並返回這個promise對象。


2. 使用 Promise.reject

  • Promise.reject(error) 是和 Promise.resolve(value) 相似的靜態方法,是 new Promise() 方 法的快捷方式。
new Promise(
    function (resolve,reject) { 
        reject(new Error("出錯了"));
    }
);
// 是Promise.reject(newError("出錯了"))的語法糖。
//以下:
Promise.reject(new Error("BOOM!")).catch(
    function(error){ 
        console.error(error);
    }
);
複製代碼

上一篇寫了一部分,再寫一些關於promise的筆記

Promise.then() &&鏈式寫法

  • promise能夠寫成方法鏈的形式
aPromise.then(function taskA(value) { 
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){ //最後catch 統一捕捉異常
console.log(error);
});

複製代碼

來段代碼,看下鏈式操做的執行流程

function taskA() { 
   console.log("Task A");
}
function taskB() {
   console.log("Task B"); 
}
function onRejected(error) { 
   console.log("Catch Error: A or B", error);
}
function finalTask() {
   console.log("Final Task"); 
}
var promise = Promise.resolve(); 
promise.then(taskA)
       .then(taskB)
       .catch(onRejected)
       .then(finalTask);

複製代碼

上面這段代碼的執行流程以下圖:

此處輸入圖片的描述

註釋: .cntch() 能夠捕捉 taskA taskB出現的異常,可是 onRejected、finalTask這兩個後面沒有catch捕捉了,因此這兩個函數出現問題不會被捕捉到。

  • 用then、catch 鏈式寫法,要比純then寫法好!!
    • catch 能夠捕捉到resolve函數裏面的錯誤,有時候我能夠主動在resolve函數裏throw一個錯誤或者返回一個Rejected狀態的promise對象(推薦後者);
    • 鏈式操做代碼更簡潔

Promise鏈式寫法中參數如何傳遞

前面例子中的Task都是相互獨立的,只是被簡單調用而已。這時候若是 Task A 想給 Task B 傳遞一個參數,那就是在 Task A 中 return 的返回值,會在 Task B 執行時傳給它。

function doubleUp(value) { 
    return value * 2;
}
function increment(value) {
    return value + 1; 
}
function output(value) { 
    console.log(value);
}
var promise = Promise.resolve(1); 
promise
.then(increment) 
.then(doubleUp) 
.then(output) 
.catch(function(error){
    // promise chain中出現異常的時候會被調用
    console.error(error); 
});

 1. Promise.resolve(1); 傳遞 1 給 increment 函數
 2. 函數 increment 對接收的參數進行 +1 操做並返回(經過 return ) 3. 這時參數變爲2,並再次傳給 doubleUp 函數
 3. 最後在函數 output 中打印結果
複製代碼

**注:**每一個方法中return的值不只只侷限於字符串或者數值類型,也能夠是對象或者promise 對象等複雜類型。

  1. return的值會由Promise.resolve進行相應的包裝處理,所以不回調函數中會返回一個什麼樣的值,最終的結果都是返回一個新建立的promise對象。

Promise.catch 的一些小問題

  1. IE8 兼容問題catch是ECMAScrip3中保留字是不能做爲對象的屬性名使用的(ie8大概是基於es3實現的)能夠用Promise['catch']來調用(不過不少壓縮、打包工具自帶轉換功能,還不錯)

Promise處理多個異步任務,promise.all 上場

Promise.all 接收一個promise對象的數組做爲參數,當這個數組裏的全部promise對象 所有變爲resolve或reject狀態的時候,它纔會去調用 .then 方法。

// `delay`毫秒後執行resolve 
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay); }, delay
        );
    }); 
}

var startDate = Date.now();
// 全部promise變爲resolve後程序退出 
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32), 
    timerPromisefy(64), 
    timerPromisefy(128)
]).then(function (values) { 
    console.log(Date.now() - startDate + 'ms'); // 約128ms
    console.log(values); // [1,32,64,128]
    }
);
複製代碼

**注:**從總用時120ms來看,傳遞給Promise.all的promise並非一個個的順序執行的,而是 同時開始、並行執行的。


Promise處理多個異步任務,Promise.race 上場

它的使用方法和Promise.all同樣,接收一個promise對象數組爲參數

Promise.all 在接收到的全部的對象promise都變爲 FulFilled 或者 Rejected 狀態以後纔會繼續進行後面的處理,與之相對的是Promise.race 只要有一個promise對象進入FulFilled或者Rejected狀態的話,就會繼續進行後面的處理。

Promise.race([
timerPromisefy(1), 
timerPromisefy(32), 
timerPromisefy(64), 
timerPromisefy(128)
]).then(function (value) { 
    console.log(value); // => 1 只打印出一個最早出結果的
});

複製代碼
相關文章
相關標籤/搜索