JavaScript之Promise對象

来源:https://www.cnblogs.com/laixiangran/archive/2018/04/19/8885806.html
-Advertisement-
Play Games

含義 Promise 是非同步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。 Promise 對象是一個代理對象(代理一個值),被代理的值在 Promise 對象創建時可能是未 ...


含義

Promise 是非同步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。

Promise 對象是一個代理對象(代理一個值),被代理的值在 Promise 對象創建時可能是未知的。它允許你為非同步操作的成功和失敗分別綁定相應的處理方法(handlers)。 這讓非同步方法可以像同步方法那樣返回值,但並不是立即返回最終執行結果,而是一個能代表未來出現的結果的 Promise 對象。

Promise 對象有以下兩個特點:

  1. 對象的狀態不受外界影響。Promise 對象代表一個非同步操作,有三種狀態:pending(進行中)fulfilled(已成功)rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
  2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise 對象的狀態改變,只有兩種可能:從 pending 變為 fulfilled從 pending 變為 rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對 Promise 對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

基本用法

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

Promise 對象的初始化接收一個執行函數 executor,executor 是帶有 resolve 和 reject 兩個參數的函數 。

Promise 構造函數執行時會立即調用 executor 函數, resolve 和 reject 兩個函數作為參數傳遞給 executor(executor 函數在 Promise 構造函數返回新建對象前被調用)。

resolve 和 reject 函數被調用時,分別將 promise 的狀態改為 fulfilled(完成)rejected(失敗)。executor 內部通常會執行一些非同步操作,一旦完成,可以調用 resolve 函數來將 promise 狀態改成 fulfilled,或者在發生錯誤時將它的狀態改為 rejected

如果在 executor 函數中拋出一個錯誤,那麼該 promise 狀態為 rejected。executor函數的返回值被忽略。

先看個示例:(註:後文的示例均使用 setTimeout 模擬非同步操作

// 從 pending 變為 fulfilled
var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        resolve('promise fulfilled!');
    }, 500);
}).then(function(data) {
    console.log(data);
});
// Hi,
// promise fulfilled!

// 從 pending 變為 rejected
var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        reject('promise rejected!');
    }, 500);
}).then(null, function(error) {
    console.log(error);
});
// Hi,
// promise rejected!

解釋一下 從 pending 變為 fulfilled 這段代碼,當執行 new Promise() 時,傳入的執行函數就立即執行了,此時其內部有一個非同步操作(過 500ms 之後執行),等過了 500ms 之後先執行 console.log('Hi,'); 輸出 Hi,,此時 promise 的狀態為 pending(進行中),而執行 resolve('Promise!'); 則修改 promise 的狀態為 fulfilled(完成),然後我們調用 then() 接收 promise 在 fulfilled 狀態下傳遞的值,此時輸出 'Promise!'

同理,從 pending 變為 rejected 這段代碼基本差不多,不同的是非同步操作調用了 reject 方法,then 方法使用第二個參數接收 rejected 狀態下傳遞的值。

Promise.prototype.then()

then 的作用是為 Promise 實例添加狀態改變時的回調函數。

then 方法的第一個參數是 resolved 狀態的回調函數,第二個參數(可選)是 rejected 狀態的回調函數。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態為200代表成功,不是200代表失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}, function(error) {
    console.log(error);
});
// 如果調用 resolve 方法,輸出如下:
// Hi,
// promise fulfilled!

// 如果調用 reject 方法,輸出如下:
// Hi,
// promise rejected!

then 方法返回的是一個新的 Promise 實例(註意,不是原來那個Promise實例)。因此可以採用鏈式寫法,即 then 方法後面再調用另一個 then 方法。採用鏈式的 then,可以指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的還是一個 Promise 對象(即有非同步操作),這時後一個回調函數,就會等待該 Promise 對象的狀態發生變化,才會被調用。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');
        resolve();
    }, 500);
}).then(function() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            // 模擬請求,請求狀態為200代表成功,不是200代表失敗
            if (status === 200) {
                resolve('promise fulfilled!');
            } else {
                reject('promise rejected!');
            }
        });
    })
}).then(function(data) {
    console.log(data);
}, function(error) {
    console.log(error);
});
// 如果第一個 then 調用 resolve 方法,第二個 then 調用第一個回調函數,最終輸出如下:
// Hi,
// promise fulfilled!

// 如果第一個 then 調用 reject 方法,第二個 then 調用第一個回調函數,最終輸出如下:
// Hi,
// promise rejected!

Promise.prototype.catch()

catch 方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

所以下麵代碼:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態為200代表成功,不是200代表失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}, function(error) {
    console.log(error);
});

等價於:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態為200代表成功,不是200代表失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}).catch(function(error) {
    console.log(error);
});

如果沒有使用 catch 方法或者 then 第二個參數指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應,這跟傳統的 try/catch 代碼塊是不同。

catch 方法返回的還是一個 Promise 對象,因此後面還可以接著調用 then 方法。

catch 方法與 .then(null, rejection) 的不同:

  • 如果非同步操作拋出錯誤,狀態就會變為 rejected,就會調用 catch 方法指定的回調函數,處理這個錯誤。
  • then 方法指定的回調函數,如果運行中拋出錯誤,也會被 catch 方法捕獲。
  • catch 方法的寫法更接近同步的寫法(try/catch)。

因此,建議總是使用 catch 方法,而不使用 then 方法的第二個參數。

Promise.prototype.finally()

finally 方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('Hi,');

        // 模擬請求,請求狀態為200代表成功,不是200代表失敗
        if (status === 200) {
            resolve('promise fulfilled!');
        } else {
            reject('promise rejected!');
        }
    }, 500);
}).then(function(data) {
    console.log(data);
}).catch(function(error) {
    console.log(error);
}).finally(function() {
    console.log('I am finally!');
});

上面代碼中,不管 promise 最後的狀態,在執行完 then 或 catch 指定的回調函數以後,都會執行 finally 方法指定的回調函數。

Promise.all()

Promise.all 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

var p = Promise.all([p1, p2]);

上面代碼中,Promise.all 方法接受一個數組作為參數,p1、p2 都是 Promise 實例,如果不是,就會先調用下麵講到的 Promise.resolve 方法,將參數轉為 Promise 實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 實例。)

p的狀態由p1、p2決定,分成兩種情況。

(1)只有 p1、p2 的狀態都變成 fulfilled,p 的狀態才會變成 fulfilled,此時 p1、p2 的返回值組成一個數組,傳遞給 p 的回調函數。

(2)只要 p1、p2 之中有一個被 rejected,p 的狀態就變成 rejected,此時第一個被 reject 的實例的返回值,會傳遞給 p 的回調函數。

示例:

試想一個頁面聊天系統,我們需要從兩個不同的 URL 分別獲得用戶的個人信息和好友列表,這兩個任務是可以並行執行的,用Promise.all()實現。

// 並行執行非同步任務
var p1 = new Promise(function (resolve, reject) {
    setTimeout(function() {
        // 模擬請求,請求狀態為200代表成功,不是200代表失敗
        if (status === 200) {
            resolve('P1');
        } else {
            reject('error');
        }
    }, 500);
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同時執行p1和p2,併在它們都完成後執行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 輸出:['P1', 'P2']
}).catch(function(error) {
    console.log(error); // 如果p1執行失敗,則輸出:error
});

註意,如果作為參數的 Promise 實例,自己定義了 catch 方法,那麼它一旦被 rejected,並不會觸發 Promise.all() 的 catch 方法。

Promise.race()

Promise.race 方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

var p = Promise.race([p1, p2]);

上面代碼中,只要 p1、p2 之中有一個實例率先改變狀態,p 的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給 p 的回調函數。

Promise.race 方法的參數與 Promise.all 方法一樣,如果不是 Promise 實例,就會先調用下麵講到的 Promise.resolve 方法,將參數轉為 Promise 實例,再進一步處理。

示例:

有些時候,多個非同步任務是為了容錯。比如,同時向兩個 URL 讀取用戶的個人信息,只需要獲得先返回的結果即可。這種情況下,用Promise.race()實現。

// 多任務容錯
var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 400, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P2'
});

Promise.resolve()

有時需要將現有對象轉為 Promise 對象,Promise.resolve 方法就起到這個作用。

Promise.resolve方法的參數分成四種情況:

(1)參數是一個 Promise 實例

如果參數是 Promise 實例,那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。

(2)參數是一個 thenable 對象

thenable 對象指的是具有 then 方法的對象,比如下麵這個對象。

var thenable = {
    then: function (resolve, reject) {
        resolve(42);
    }
};

Promise.resolve 方法會將這個對象轉為 Promise 對象,然後就立即執行 thenable 對象的 then 方法。

var thenable = {
    then: function (resolve, reject) {
        resolve(42);
    }
};

var p1 = Promise.resolve(thenable);
p1.then(function (value) {
    console.log(value);  // 42
});

上面代碼中,thenable 對象的 then 方法執行後,對象 p1 的狀態就變為 resolved,從而立即執行最後那個 then 方法指定的回調函數,輸出 42。

(3)參數不是具有 then 方法的對象,或根本就不是對象

如果參數是一個原始值,或者是一個不具有 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態為 resolved。

var p = Promise.resolve('Hello');

p.then(function (s) {
    console.log(s)
});
// 'Hello'

var p1 = Promise.resolve(true);

p1.then(function (b) {
    console.log(b)
});
// true

var p2 = Promise.resolve(1);

p1.then(function (n) {
    console.log(n)
});
// 1

(4)不帶有任何參數

Promise.resolve 方法允許調用時不帶參數,直接返回一個 resolved 狀態的 Promise 對象。

所以,如果希望得到一個 Promise 對象,比較方便的方法就是直接調用 Promise.resolve 方法。

Promise.reject()

Promise.reject 方法也會返回一個新的 Promise 實例,該實例的狀態為 rejected。

註意,Promise.reject 方法的參數,會原封不動地作為 reject 的參數,變成後續方法的參數。這一點與 Promise.resolve 方法不一致。

var thenable = {
    then(resolve, reject) {
        reject('出錯了');
    }
};

Promise.reject(thenable)
    .catch(e = > {
    console.log(e === thenable)
})
// true

上面代碼中,Promise.reject 方法的參數是一個 thenable 對象,執行以後,後面 catch 方法的參數不是 reject 拋出的 出錯了 這個字元串,而是 thenable 對象。

應用

載入圖片

我們可以將圖片的載入寫成一個 Promise,一旦載入完成,Promise 的狀態就發生變化。

function (path) {
    return new Promise(function (resolve, reject) {
        const image = new Image();
        image.onload = resolve;
        image.onerror = reject;
        image.src = path;
    });
};

封裝ajax

我們可以將 ajax 請求寫成一個 Promise,根據請求的不同狀態改變 Promise 的狀態。

function ajax(method, url, data) {
    var request = new XMLHttpRequest();
    return new Promise(function (resolve, reject) {
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    resolve(request.responseText);
                } else {
                    reject(request.status);
                }
            }
        };
        request.open(method, url);
        request.send(data);
    });
}

總結

優點:

  • 可以將非同步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數(回調地獄)。
  • 在非同步執行的流程中,可以把執行代碼和處理結果的代碼清晰地分離開來。

缺點:

  • 無法取消 Promise,一旦新建它就會立即執行,無法中途取消。
  • 如果不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。
  • 當處於 pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

參考資料

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

http://es6.ruanyifeng.com/#docs/promise

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 jest 是 facebook 開源的,用來進行單元測試的框架,可以測試 javascipt 和 react。 單元測試各種好處已經被說爛了,這裡就不多扯了。重點要說的是,使用 jest, 可以降低寫單元測試的難度。 單元測試做得好,能夠極大提高軟體的質量,加快軟體迭代更新的速度, 但是,單元 ...
  • 《21天網站建設實錄》以網頁設計師的項目開發為背景,以“阿裡里線上購物”商業網站的開發過程為流程,通過21天的任務期限,以一天一項任務、一天掌握一項技能項目實戰的學習模式,全面講解了一個網站立項、策劃、製作、完善、優化、上傳及維護等環節的完整過程,詳細敘述了商業網站開發的一般性知識和網站建設所涉及的 ...
  • 很多從事Web前端開發的人對HTML總有些不滿,比如需要手動檢查和設計很多格式代碼,不僅容易出錯,而且存在大量重覆。好在HTML5讓我們看到了曙光。作為下一代Web開發標準,HTML5成為主流的日子已經不遠。它對音頻視頻、表單驗證、事件處理、繪圖等的支持都讓我們非常期待,視頻音頻的播放、表單檢查和提 ...
  • HTML5與CSS3基礎教程(第7版)試讀不僅介紹了文本、圖像、鏈接、列表、表格、表單、多媒體等網頁元素,也介紹瞭如何為網頁設計結構、佈局,添加動態效果、格式化等形式,此外還涉及調試和發佈、聚合和吸引訪問等。書中詳細講解了視頻、音頻及其他新增特性,從零開始教會讀者創建漸進增強的普適性網站。書中提供了 ...
  • 《HTML5與CSS3基礎教程(第8版)》自第1版至今,一直是講解HTML和CSS入門知識的經典暢銷書,全面系統地闡述HTML5和CSS3基礎知識以及實際運用技術,通過大量實例深入淺出地分析了網頁製作的方方面面。最新第8版不僅介紹了文本、圖像、鏈接、列表、表格、表單等網頁元素,還介紹瞭如何為網頁設計 ...
  • 一、在本地新建一個文件js文件 JS代碼: 二、設置快捷鍵 將上述js文件設置一個快捷鍵到桌面,然後點擊文件屬性設置快捷鍵,你可以使用任何和其他快捷鍵不同的組合鍵。如下圖: 三、效果 在桌面按下方纔設置的快捷鍵,如同時按下Ctrl、Alt和left鍵,效果如下: 按回車即可進行電腦的快速關機。 四、 ...
  • jQuery的touch事件是當用戶觸摸事件(頁面)時觸發的。 jQuery的click事件是當用戶點擊元素時觸發的。 而事件執行流程是手指點擊一個元素,會經過:touchstart --> touchmove -> touchend --》click。所以在觸發touch事件時,預設會自動觸發cl ...
  • tooltip.css 純CSS滑鼠提示工具。 v. 2.0.0 更新日期:2018.4.12 預覽DEMO。 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...