日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

再谈 Promise

發(fā)布時(shí)間:2025/6/17 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 再谈 Promise 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

讀完這篇文章,預(yù)計(jì)會消耗你 40 分鐘的時(shí)間。

Ajax 出現(xiàn)的時(shí)候,刮來了一陣異步之風(fēng),現(xiàn)在 Nodejs 火爆,又一陣異步狂風(fēng)刮了過來。需求是越來越苛刻,用戶對性能的要求也是越來越高,隨之而來的是頁面異步操作指數(shù)般增長,如果不能恰當(dāng)?shù)目刂拼a邏輯,我們就會陷入無窮的回調(diào)地獄中。

ECMAScript 6 已經(jīng)將異步操作納入了規(guī)范,現(xiàn)代瀏覽器也內(nèi)置了 Promise 對象供我們進(jìn)行異步編程,那么此刻,還在等啥?趕緊學(xué)習(xí)學(xué)習(xí) Promise 的內(nèi)部原理吧!

第一章 了解 Promise

一、場景再現(xiàn)

由于 javascript 的單線程性質(zhì),我們必須等待上一個(gè)事件執(zhí)行完成才能處理下一步,如下:

// DOM ready之后執(zhí)行 $(document).ready(function(){// 獲取模板$.get(url, function(tpl){// 獲取數(shù)據(jù)$.get(url2, function(data){// 構(gòu)建 DOMStringmakeHtml(tpl, data, function(str){// 插入到 DOM 中$(obj).html(str);});});}); });

為了減少首屏數(shù)據(jù)的加載,我們將一些模板和所有數(shù)據(jù)都放在服務(wù)器端,當(dāng)用戶操作某個(gè)按鈕時(shí),需要將模板和數(shù)據(jù)拼接起來插入到 DOM 中,這個(gè)過程還必須在 DOMReady 之后才能執(zhí)行。這種情況是十分常見的,如果異步操作再多一些,整個(gè)代碼的縮進(jìn)讓人看著很不舒服,為了優(yōu)雅地處理這個(gè)問題,ECMAScript 6 引入了 Promise 的概念,目前一些現(xiàn)代瀏覽器已經(jīng)支持這些新東西了!

二、模型

為了讓代碼流程更加清晰,我們假想著能夠按照下面的流程來跑程序:

new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();

先將要事務(wù)按照執(zhí)行順序依次 push 到事務(wù)隊(duì)列中,push 完了之后再通過 resolve 函數(shù)啟動(dòng)整個(gè)流程。

整個(gè)流程的操作模型如下:

promise(ok).then(ok_1).then(ok_2).then(ok_3).reslove(value)------+| | | | || | | | +=======+ || | | | | | || | | | | | |+---------|----------|----------|--------→ ok() ←------+| | | | ↓ || | | | ↓ |+----------|----------|--------→ ok_1()|| | | ↓ || | | ↓ |+----------|--------→ ok_2()|| | ↓ || | ↓ |+--------→ ok_3()-----+| | | | | ↓ @ Created By Barret Lee +=======+ exit

在 resolve 之前,promise 的每一個(gè) then 都會將回調(diào)函數(shù)壓入隊(duì)列,resolve 后,將 resolve 的值送給隊(duì)列的第一個(gè)函數(shù),第一個(gè)函數(shù)執(zhí)行完畢后,將執(zhí)行結(jié)果再送入下一個(gè)函數(shù),依次執(zhí)行完隊(duì)列。一連串下來,一氣呵成,沒有絲毫間斷。

三、簡單的封裝

如果了解 Promise,可以移步下方,看看對 Promise 的封裝:

Github:?https://github.com/barretlee/myPromise
DEMO:?http://barretlee.github.io/myPromise/index.html

如果還不是很了解,可以往下閱讀全文,了解一二。

第二章 Promise 原理

一、什么是 Promise ?

那么,什么是 Promise ?

Promise 可以簡單理解為一個(gè)事務(wù),這個(gè)事務(wù)存在三種狀態(tài):

  • 已經(jīng)完成了 resolved
  • 因?yàn)槟撤N原因被中斷了 rejected
  • 還在等待上一個(gè)事務(wù)結(jié)束 pending
  • 上文中我們舉了一個(gè)栗子,獲取模板和數(shù)據(jù)之后再將拼合的數(shù)據(jù)插入到 DOM 中,這里我們將整個(gè)程序分解成多個(gè)事務(wù):

    事務(wù)一: 獲取模板↓ 事務(wù)二: 獲取數(shù)據(jù)↓ 事務(wù)三: 拼合之后插入到 DOM

    在事務(wù)一結(jié)束之前,也就是模板代碼從服務(wù)器拉取過來之前,事務(wù)二和事務(wù)三都處于 pending 狀態(tài),他們必須等待上一個(gè)事務(wù)結(jié)束。而事務(wù)一結(jié)束之后會將自身狀態(tài)標(biāo)記為 resolved,并把該事務(wù)中處理的結(jié)果移交給事務(wù)二繼續(xù)處理(當(dāng)然,這里如果沒有數(shù)據(jù)返回,事務(wù)二就不會獲得上一個(gè)事務(wù)的數(shù)據(jù)),依次類推,直到最后一個(gè)事務(wù)操作結(jié)束。

    在事務(wù)操作的過程中,若遇到錯(cuò)誤,比如事務(wù)一獲取數(shù)據(jù)存在跨域問題,那事務(wù)就會操作失敗,此時(shí)它會將自身的狀態(tài)標(biāo)記為 rejected,由于后續(xù)事務(wù)都是承接前一事務(wù)的,前一事務(wù)已經(jīng)宣告工程已經(jīng)玩不成了,那么后續(xù)的所有事務(wù)都會將自己標(biāo)記為 rejected,其標(biāo)記理由(reason)就是出錯(cuò)事務(wù)的報(bào)錯(cuò)信息(這個(gè)報(bào)錯(cuò)信息可以使用 try…catch 來捕獲,也可以通過程序自身來捕獲,如 ajax 的 onerror 事件、ajax 返回的狀態(tài)碼為 404 等)。

    小結(jié):Promise 就是一個(gè)事務(wù)的管理器。他的作用就是將各種內(nèi)嵌回調(diào)的事務(wù)用流水形式表達(dá),其目的是為了簡化編程,讓代碼邏輯更加清晰。

    由于整個(gè)程序的實(shí)現(xiàn)比較難理解,對于 Promise,我們將分為兩部分闡述:

    • 無錯(cuò)誤傳遞的 Promise,也就是事務(wù)不會因?yàn)槿魏卧蛑袛?#xff0c;事務(wù)隊(duì)列中的事項(xiàng)都會被依次處理,此過程中 Promise 只有 pending 和 resolved 兩種狀態(tài),沒有 rejected 狀態(tài)。
    • 包含錯(cuò)誤的 Promise,每個(gè)事務(wù)的處理都必須使用容錯(cuò)機(jī)制來獲取結(jié)果,一旦出錯(cuò),就會將錯(cuò)誤信息傳遞給下一個(gè)事務(wù),如果錯(cuò)誤信息會影響下一個(gè)事務(wù),則下一個(gè)事務(wù)也會 rejected,如果不會,下一個(gè)事務(wù)可以正常執(zhí)行,依次類推。

    二、無錯(cuò)誤傳遞的 Promise(簡化版的 Promise)

    首先,我們需要用一個(gè)變量(status)來標(biāo)記事務(wù)的狀態(tài),然后將事務(wù)(affair)也保存到 Promise 對象中。

    var Promise = function(affair){this.state = “pending”;this.affair = affair || function(o) { return o; };this.allAffairs = []; };

    Promise 有兩個(gè)重要的方法,一個(gè)是 then,另一個(gè)是 resolve:

    • then,將事務(wù)添加到事務(wù)隊(duì)列(allAffairs)中
    • resolve,開啟流程,讓整個(gè)操作從第一個(gè)事務(wù)開始執(zhí)行

    在操作事務(wù)之前,我們會先把各種事務(wù)依次放入事務(wù)隊(duì)列中,這里會用到 then 方法:

    Promise.prototype.then = function (nextAffair){var promise = new Promise();if (this.state == ‘resloved’){// 如果當(dāng)前狀態(tài)是已完成,則這個(gè)事務(wù)將會被立即執(zhí)行return this._fire(promise, nextAffair);}else{// 否則將會被加入隊(duì)列中return this._push(promise, nextAffair);} };

    如果整個(gè)操作已經(jīng)完成了,那 then 方法送進(jìn)的事務(wù)會被立即執(zhí)行,

    Promise.prototype._fire = function (nextPromise, nextAffair){var nextResult = nextAffair(this.result);if (nextResult instanceof Promise){nextResult.then(function(obj){nextPromise.resolve(obj);});}else{nextPromise.resolve(nextResult);}return nextPromise; }; 被立即執(zhí)行之后會返回一個(gè)結(jié)果,這個(gè)結(jié)果會被傳遞到下一個(gè)事務(wù)中作為原料,但是這里需要考慮兩種情況:
  • 異步,如果這個(gè)結(jié)果也是一個(gè) Promise,則需要等待這個(gè) Promise 執(zhí)行完畢再將最終的結(jié)果傳到下一個(gè)事務(wù)中。
  • 同步,如果這個(gè)結(jié)果不是 Promise,則直接將結(jié)果傳遞給下一個(gè)事務(wù)。
  • 第一種情況還是比較常見的,比如我們在一個(gè)事務(wù)中有一個(gè)子事務(wù)隊(duì)列需要處理,此時(shí)必須等待子事務(wù)完成才能回到主事務(wù)隊(duì)列中。

    Promise.prototype.resolve = function (obj){if (this.state != ‘pending’) {throw ‘流程已完成,不能再次開啟流程!’;}this.state = ‘resloved’;// 執(zhí)行該事務(wù),并將執(zhí)行結(jié)果寄存到 Promise 管理器上this.result = this.affair(obj);for (var i = 0, len = this.allAffairs.length; i < len; ++i){// 往后執(zhí)行事務(wù)var affair = this.allAffairs[i];this._fire(affair.promise, affair.affair);}return this; };

    resolve 接受一個(gè)參數(shù),這個(gè)數(shù)據(jù)是交給第一個(gè)事務(wù)來處理的,因?yàn)榈谝粋€(gè)事務(wù)的啟動(dòng)可能需要點(diǎn)原料,這個(gè)數(shù)據(jù)就是原料,它也可以是空。該事物處理完畢之后,將操作結(jié)果(result)寄存在 Promise 對象上,方便引用,然后將結(jié)果(result)作為原料送入下一個(gè)事務(wù)。依次類推。

    我們看到 then 方法中還調(diào)用了一個(gè) _push ,這個(gè)方法的作用是將事務(wù)推進(jìn)事務(wù)管理器(Promise)。

    Promise.prototype._push = function (nextPromise, nextAffair){this.allAffairs.push({promise: nextPromise,affair: nextAffair});return nextPromise; };

    以上操作,我們就實(shí)現(xiàn)了一個(gè)簡單的事務(wù)管理器,可以測試下下面的代碼:

    // 初始化事務(wù)管理器 var promise = new Promise(function(data){console.log(data);return 1; }); // 添加事務(wù) promise.then(function(data){console.log(data);return 2; }).then(function(data){console.log(data);return 3; }).then(function(data){console.log(data);console.log(“end”); }); // 啟動(dòng)事務(wù) promise.resolve(“start”);

    可以看到依次輸出的結(jié)果為:

    > start > 1 > 2 > 3 > end

    由于上述實(shí)現(xiàn)十分簡陋,鏈?zhǔn)秸{(diào)用沒做太好的處理,請讀者自行完善:)

    下面是一個(gè)異步操作演示:

    var promise = new Promise(function(data){console.log(data);return “end”; }); promise.then(function(data){// 這里需要返回一個(gè) Promise,讓主事務(wù)切換到子事務(wù)處理return (function(data){// 創(chuàng)建一個(gè)子事務(wù)var promise = new Promise();setTimeout(function(){console.log(data);// 一秒之后才啟動(dòng)子事務(wù),模擬異步延時(shí)promise.resolve();}, 1000);return promise;})(data); }); promise.resolve(“start”); 可以看到依次輸出的結(jié)果為: > start > end (1s之后輸出)

    將函數(shù)寫的稍微好看點(diǎn):

    function delay(data){// 創(chuàng)建一個(gè)子事務(wù)var promise = new Promise();setTimeout(function(){console.log(data);// 一秒之后才啟動(dòng)子事務(wù),模擬異步延時(shí)promise.resolve();}, 1000);return promise; } // 主事務(wù) var promise = new Promise(function(data){console.log(data);return “end”; }); promise.then(delay); promise.resolve(“start”);

    三、包含錯(cuò)誤傳遞的 Promise

    真的很羨慕你能看到這么詳細(xì)的文章,當(dāng)然,后面會更加精彩!

    沒有錯(cuò)誤處理的 Promise 只能算是一個(gè)半成品,雖說可以通過在最外層加一個(gè) try..catch 來捕獲錯(cuò)誤,但沒法具體定位是哪個(gè)事務(wù)發(fā)生的錯(cuò)誤。并且這里的錯(cuò)誤不僅僅包含 JavaScript Error,還有諸如 ajax 返回的 data code 不是 200 的情況等。

    先看一個(gè)瀏覽器內(nèi)置 Promise 的實(shí)例(該代碼可在現(xiàn)代瀏覽器下運(yùn)行):

    new Promise(function(resolve, reject){resolve(“start”); }).then(function(data){console.log(data);throw “error”; }).catch(function(err){console.log(err);return “end”; }).then(function(data){console.log(data) }); Promise 的回調(diào)和 then 方法都是接受兩個(gè)參數(shù): new Promise(function(resolve, reject){// … });promise.then(function(value){/* code here */}, function(reason){/* code here */} );

    事務(wù)處理過程中,如果有值返回,則作為 value,傳入到 resolve 函數(shù)中,若有錯(cuò)誤產(chǎn)生,則作為 reason 傳入到 reject 函數(shù)中處理。

    在初始化 Promise 對象時(shí),若傳入的回調(diào)中沒有執(zhí)行 resolve 或者 reject,這需要我們主動(dòng)去啟動(dòng)事務(wù)隊(duì)列。

    promise.resolve(); promise.reject();

    上面兩種都是可以啟動(dòng)一個(gè)隊(duì)列的。這里跟第二章第二節(jié)的 resolve 函數(shù)用法類似。Promise 對象還提供了 catch 函數(shù),起用法等價(jià)于下面所示:

    promise.catch(); // 等價(jià)于 promise.then(null, function(reason){});

    還有兩個(gè) API:

    promise.all(); promise.race();

    后續(xù)再講。先看看這個(gè)有錯(cuò)誤處理的 Promise 是如何實(shí)現(xiàn)的。

    function Promise(resolver){this.status = “pending”;this.value = null;this.handlers = [];this._doPromise.call(this, resolver); }

    _doPromise 方法在實(shí)例化 Promise 函數(shù)時(shí)就執(zhí)行。如果送入的回調(diào)函數(shù) resolver 中已經(jīng) resolve 或者 reject 了,程序就已經(jīng)啟動(dòng)了,所以在實(shí)例化的時(shí)候就開始判斷。

    _doPromise: function(resolver){var called = false, self = this;try{resolver(function(value){// 如果沒有 call 則繼續(xù),并標(biāo)記 called 為 true!called && (called = !0, self.resolve(value));}, function(reason){// 同上!called && (called = !0, self.reject(reason));});} catch(e) {// 同上,捕獲錯(cuò)誤,傳遞錯(cuò)誤到下一個(gè) then 事務(wù)!called && (called = !0, self.reject(e));} },

    只要 resolve 或者 reject 就會標(biāo)記程序 called 為 true,表示程序已經(jīng)啟動(dòng)了。

    resolve: function(value) {try{if(this === value){throw new TypeError(‘流程已完成,不能再次開啟流程!’);} else {// 如果還有子事務(wù)隊(duì)列,繼續(xù)執(zhí)行value && value.then && this._doPromise(value.then);}// 執(zhí)行完了之后標(biāo)記為完成this.status = “fulfilled”;this.value = value;this._dequeue();} catch(e) {this.reject(e);} }, reject: function(reason) {// 標(biāo)記狀態(tài)為出錯(cuò)this.status = “rejected”;this.value = reason;this._dequeue(); },

    可以看到,每次 resolve 的時(shí)候都會用一個(gè) try..catch 包裹來捕獲未知錯(cuò)誤。

    _dequeue: function(){var handler;// 執(zhí)行事務(wù),直到隊(duì)列為空while (this.handlers.length) {handler = this.handlers.shift();this._handle(handler.thenPromise, handler.onFulfilled, handler.onRejected);} },

    無論是 resolve 還是 reject 都會讓程序往后奔流,直到結(jié)束所有事務(wù),所以這兩個(gè)方法中都有 _dequeue 函數(shù)。

    _handle: function(thenPromise, onFulfilled, onRejected){var self = this;setTimeout(function() {// 判斷下次操作采用哪個(gè)函數(shù),reject 還是 resolvevar callback = self.status == “fulfilled” ? onFulfilled : onRejected;// 只有是函數(shù)才會繼續(xù)回調(diào)if (typeof callback === ‘function’) {try {self.resolve.call(thenPromise, callback(self.value));} catch(e) {self.reject.call(thenPromise, e);}return;}// 否則就將 value 傳遞給下一個(gè)事務(wù)了self.status == “fulfilled”? self.resolve.call(thenPromise, self.value) : self.reject.call(thenPromise, self.value);}, 1); },

    這個(gè)函數(shù)跟上一節(jié)提到的 _fire 類似,如果 callback 是 function,就會進(jìn)入子事務(wù)隊(duì)列,處理完了之后退回到主事務(wù)隊(duì)列。最后一個(gè) then 方法,將事務(wù)推進(jìn)隊(duì)列。

    then: function(onFulfilled, onRejected){var thenPromise = new Promise(function() {});if (this.status == “pending”) {this.handlers.push({thenPromise: thenPromise,onFulfilled: onFulfilled,onRejected: onRejected});} else {this._handle(thenPromise, onFulfilled, onRejected);}return thenPromise; }

    如果第二節(jié)沒有理解清楚,這一節(jié)也會讓人頭疼,這一部分講的比較粗糙。

    第三章 異步編程

    一、jQuery 中的 Defferred 對象

    或許你在面試的時(shí)候,有面試官問你:

    $.ajax()?執(zhí)行后返回的結(jié)果是什么?

    在 jQuery1.5 版本就已經(jīng)引入了 Defferred 對象,當(dāng)時(shí)為了引入這個(gè)東西,整個(gè) jQuery 都被重構(gòu)了。Defferred 跟 Promise 類似,它表示一個(gè)還未完成任務(wù)的對象,而 Promise 確切的說,是一個(gè)代表未知值的對象。

    $.ajax({url: url }).done(function(data, status, xhr){//… }).fail(function(){//… });

    回憶下第二章第一節(jié)中的 Promise,是不是如出一轍,只是 jQuery 還提供了更多的語法糖:

    $.ajax({url: url,success: function(data){//…},error: funtion(){//…} });

    他允許將 done 和 fail 兩個(gè)函數(shù)的回調(diào)放在 ajax 初始化的參數(shù) success 和 fail 上,其原理還是一樣的,同樣,還有這樣的東西:

    $.when(taskOne, taskTwo).done(function () {console.log(“都執(zhí)行完畢后才會輸出我!”); }).fail(function(){console.log(“只要有一個(gè)失敗,就會輸出我!”) }); 當(dāng) taskOne 和 taskTwo 都完成之后才執(zhí)行 done 回調(diào),這個(gè)瀏覽器內(nèi)置的 Promise 也有對應(yīng)的函數(shù): Promise.all([true, Promise.resolve(1), …]).then(function(value){//.... });

    瀏覽器內(nèi)置的 Promise 還提供了一個(gè) API:

    Promise.race([true, Promise.resolve(1), …]).then(function(value){//.... }, function(reason){//… });

    只要 race 參數(shù)中有一個(gè) resolve 或者 reject,then 回調(diào)就會出發(fā)。

    二、基于事件響應(yīng)的異步模型

    @樸靈?寫的?EventProxy?就是基于事件響應(yīng)的異步模型,按理說,這個(gè)實(shí)現(xiàn)的邏輯是最清晰的,不過代碼量稍微多一點(diǎn)。

    function taskA(){setTimeout(function(){var result = “A”;E.emit(“taskA”, result);}, 1000); }function taskB(){setTimeout(function(){var result = “B”;E.emit(“taskB”, result);}, 1000); }E.all([“taskA”, “taskB”], function(A, B){return A + B; });

    我沒有看他的源碼,但是想想,應(yīng)該是這個(gè)邏輯。只需要在消息中心管理各個(gè) emit 以及消息注冊。這里的錯(cuò)誤處理值得思考下。

    在半年前,也寫過一篇關(guān)于異步編程的文章:JavaScript異步編程原理,感興趣的可以去讀一讀。

    第四章 小結(jié)

    一、小結(jié)

    文章比較長,閱讀了好幾天別人寫的東西,自己提筆還是比較輕松的,本文大概花費(fèi)了 6 個(gè)小時(shí)撰寫。

    本文主要解說了 Promise 的應(yīng)用場景和實(shí)現(xiàn)原理,如果你能夠順暢的讀完全文并且之處文中的一些錯(cuò)誤,說明你已經(jīng)悟到了:)

    Promise 使用起來不難,但是理解其原理還是有點(diǎn)偏頭痛的,所以下面列舉的幾篇相關(guān)閱讀也建議讀者點(diǎn)進(jìn)去看看。

    轉(zhuǎn)載于:https://www.cnblogs.com/libin-1/p/5919650.html

    總結(jié)

    以上是生活随笔為你收集整理的再谈 Promise的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。