摘要:請(qǐng)求的傳統(tǒng)寫法改為后的寫法很顯然,我們把異步中使用回調(diào)函數(shù)的場(chǎng)景改為了等函數(shù)鏈?zhǔn)秸{(diào)用的方式。數(shù)組中第一個(gè)元素是異步的,第二個(gè)是非異步,會(huì)立即改變狀態(tài),所以新對(duì)象會(huì)立即改變狀態(tài)并把傳遞給成功時(shí)的回調(diào)函數(shù)。
前言
Promise,相信每一個(gè)前端工程師都或多或少地在項(xiàng)目中都是用過(guò),畢竟它早已不是一個(gè)新名詞。ES6中已經(jīng)原生對(duì)它加以支持,在caniuse中搜索一下Promise,發(fā)現(xiàn)新版的chrome和firefox也已經(jīng)支持。但是低版本的瀏覽器我們可以使用es6-promise這個(gè)polyfill庫(kù)來(lái)加以兼容。
暫且不談await、async,在Google或百度或360搜索等搜索引擎、或者在segmentfault等社區(qū)中,我們可以搜到一大把介紹promise的文章,畢竟它已經(jīng)出現(xiàn)了很長(zhǎng)時(shí)間,早已有很多大神分析講解過(guò)。
我也看了一些文章,但是感覺(jué)都沒(méi)有達(dá)到想要的效果。所以決定自己開(kāi)一個(gè)小系列文章學(xué)習(xí)講解一下promise的原理,以及實(shí)現(xiàn),最后再談一談與之聯(lián)系密切的Deferred對(duì)象。
本文是該系列的第一篇文章,主要先讓大家對(duì)Promise有一個(gè)基本的認(rèn)識(shí)。
promise簡(jiǎn)介Promise的出現(xiàn),原本是為了解決回調(diào)地獄的問(wèn)題。所有人在講解Promise時(shí),都會(huì)以一個(gè)ajax請(qǐng)求為例,此處我們也用一個(gè)簡(jiǎn)單的ajax的例子來(lái)帶大家看一下Promise是如何使用的。
ajax請(qǐng)求的傳統(tǒng)寫法:
getData(method, url, successFun, failFun){ var xmlHttp = new XMLHttpRequest(); xmlHttp.open(method, url); xmlHttp.send(); xmlHttp.onload = function () { if (this.status == 200 ) { successFun(this.response); } else { failFun(this.statusText); } }; xmlHttp.onerror = function () { failFun(this.statusText); }; }
改為promise后的寫法:
getData(method, url){ var promise = new Promise(function(resolve, reject){ var xmlHttp = new XMLHttpRequest(); xmlHttp.open(method, url); xmlHttp.send(); xmlHttp.onload = function () { if (this.status == 200 ) { resolve(this.response); } else { reject(this.statusText); } }; xmlHttp.onerror = function () { reject(this.statusText); }; }) return promise; } getData("get","www.xxx.com").then(successFun, failFun)
很顯然,我們把異步中使用回調(diào)函數(shù)的場(chǎng)景改為了.then()、.catch()等函數(shù)鏈?zhǔn)秸{(diào)用的方式。基于promise我們可以把復(fù)雜的異步回調(diào)處理方式進(jìn)行模塊化。
下面,我們就來(lái)介紹一下Promise到底是個(gè)什么東西?它是如何做到的?
Promise的原理分析其實(shí)promise原理說(shuō)起來(lái)并不難,它內(nèi)部有三個(gè)狀態(tài),分別是pending,fulfilled和rejected 。
pending是對(duì)象創(chuàng)建后的初始狀態(tài),當(dāng)對(duì)象fulfill(成功)時(shí)變?yōu)?b>fulfilled,當(dāng)對(duì)象reject(失敗)時(shí)變?yōu)?b>rejected。且只能從pengding變?yōu)?b>fulfilled或rejected ,而不能逆向或從fulfilled變?yōu)?b>rejected 、從rejected變?yōu)?b>fulfilled。如圖所示:
Promise實(shí)例方法介紹Promise對(duì)象擁有兩個(gè)實(shí)例方法then()和catch()。
從前面的例子中可以看到,成功和失敗的回調(diào)函數(shù)我們是通過(guò)then()添加,在promise狀態(tài)改變時(shí)分別調(diào)用。promise構(gòu)造函數(shù)中通常都是異步的,所以then方法往往都先于resolve和reject方法執(zhí)行。所以promise內(nèi)部需要有一個(gè)存儲(chǔ)fulfill時(shí)調(diào)用函數(shù)的數(shù)組和一個(gè)存儲(chǔ)reject時(shí)調(diào)用函數(shù)的數(shù)組。
從上面的例子中我們還可以看到then方法可以接收兩個(gè)參數(shù),且通常都是函數(shù)(非函數(shù)時(shí)如何處理下一篇文章中會(huì)詳細(xì)介紹)。第一個(gè)參數(shù)會(huì)添加到fulfill時(shí)調(diào)用的數(shù)組中,第二個(gè)參數(shù)添加到reject時(shí)調(diào)用的數(shù)組中。當(dāng)promise狀態(tài)fulfill時(shí),會(huì)把resolve(value)中的value值傳給調(diào)用的函數(shù)中,同理,當(dāng)promise狀態(tài)reject時(shí),會(huì)把reject(reason)中的reason值傳給調(diào)用的函數(shù)。例:
var p = new Promise(function(resolve, reject){ resolve(5) }).then(function(value){ console.log(value) //5 }) var p1 = new Promise(function(resolve, reject){ reject(new Error("錯(cuò)誤")) }).then(function(value){ console.log(value) }, function(reason){ console.log(reason) //Error: 錯(cuò)誤(…) })
then方法會(huì)返回一個(gè)新的promise,下面的例子中p == p1將返回false,說(shuō)明p1是一個(gè)全新的對(duì)象。
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) }) p == p1 // false
這也是為什么then是可以鏈?zhǔn)秸{(diào)用的,它是在新的對(duì)象上添加成功或失敗的回調(diào),這與jQuery中的鏈?zhǔn)秸{(diào)用不同。
那么新對(duì)象的狀態(tài)是基于什么改變的呢?是不是說(shuō)如果p的狀態(tài)fulfill,后面的then創(chuàng)建的新對(duì)象都會(huì)成功;或者說(shuō)如果p的狀態(tài)reject,后面的then創(chuàng)建的新對(duì)象都會(huì)失敗?
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) // 5 }).then(function(value){ console.log("fulfill " + value) // fulfill undefined }, function(reason){ console.log("reject " + reason) })
上面的例子會(huì)打印出5和"fulfill undefined"說(shuō)明它的狀態(tài)變?yōu)槌晒ΑD侨绻覀冊(cè)?b>p1的then方法中拋出異常呢?
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) // 5 throw new Error("test") }).then(function(value){ console.log("fulfill " + value) }, function(reason){ console.log("reject " + reason) // reject Error: test })
理所當(dāng)然,新對(duì)象肯定會(huì)失敗。
反過(guò)來(lái)如果p失敗了,會(huì)是什么樣的呢?
var p = new Promise(function(resolve, reject){ reject(5) }) var p1 = p.then(undefined, function(value){ console.log(value) // 5 }).then(function(value){ console.log("fulfill " + value) // fulfill undefined }, function(reason){ console.log("reject " + reason) })
說(shuō)明新對(duì)象狀態(tài)不會(huì)受到前一個(gè)對(duì)象狀態(tài)的影響。
再來(lái)看如下代碼:
var p = new Promise(function(resolve, reject){ reject(5) }) var p1 = p.then(function(value){ console.log(value) }) var p2 = p1.then(function(value){ console.log("fulfill " + value) }, function(reason){ console.log("reject " + reason) // reject 5 })
我們發(fā)現(xiàn)p1的狀態(tài)變?yōu)?b>rejected,從而觸發(fā)了then方法第二個(gè)參數(shù)的函數(shù)。這似乎與我們之前提到的有差異啊,p1的狀態(tài)受到了p的狀態(tài)的影響。
再來(lái)看一個(gè)例子:
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(undefined, function(value){ console.log(value) }) var p2 = p1.then(function(value){ console.log("fulfill " + value) // fulfill 5 }, function(reason){ console.log("reject " + reason) })
細(xì)心的人可能會(huì)發(fā)現(xiàn),該例子中then第一個(gè)參數(shù)是undefined,且value值5被傳到了p1成功時(shí)的回調(diào)函數(shù)中。上面那個(gè)例子中then的第二個(gè)參數(shù)是undefined,同樣reason值也傳到了p1失敗時(shí)的回調(diào)函數(shù)中。這是因當(dāng)對(duì)應(yīng)的參數(shù)不為函數(shù)時(shí),會(huì)將前一promise的狀態(tài)和值傳遞下去。
promise含有一個(gè)實(shí)例方法catch,從名字上我們就看得出來(lái),它和異常有千絲萬(wàn)縷的關(guān)系。其實(shí)catch(onReject)方法等價(jià)于then(undefined, onReject),也就是說(shuō)如下兩種情況是等效的。
new Promise(function(resolve, reject){ reject(new Error("error")) }).then(undefined, function(reason){ console.log(reason) // Error: error(…) }) new Promise(function(resolve, reject){ reject(new Error("error")) }).catch(function(reason){ console.log(reason) // Error: error(…) })
我們提到參數(shù)不為函數(shù)時(shí)會(huì)把值和狀態(tài)傳遞下去。所以我們可以在多個(gè)then之后添加一個(gè)catch方法,這樣前面只要reject或拋出異常,都會(huì)被最后的catch方法處理。
new Promise(function(resolve, reject){ resolve(5) }).then(function(value){ taskA() }).then(function(value){ taskB() }).then(function(value){ taskC() }).catch(function(reason){ console.log(reason) })Promise的靜態(tài)方法
Promise還有四個(gè)靜態(tài)方法,分別是resolve、reject、all、race,下面我們一一介紹。
除了通過(guò)new Promise()的方式,我們還有兩種創(chuàng)建Promise對(duì)象的方法:
Promise.resolve() 它相當(dāng)于創(chuàng)建了一個(gè)立即resolve的對(duì)象。如下兩段代碼作用相同:
Promise.resolve(5) new Promise(function(resolve){ resolve(5) })
它使得promise對(duì)象直接resolve,并把5傳到后面then添加的成功函數(shù)中。
Promise.resolve(5).then(function(value){ console.log(value) // 5 })
Promise.reject() 很明顯它相當(dāng)于創(chuàng)建了一個(gè)立即reject的對(duì)象。如下兩段代碼作用相同:
Promise.reject(new Error("error")) new Promise(function(resolve, reject){ reject(new Error("error")) })
它使得promise對(duì)象直接reject,并把error傳到后面catch添加的函數(shù)中。
Promise.reject(new Error("error")).catch(function(reason){ console.log(reason) // Error: error(…) })
Promise.all() 它接收一個(gè)promise對(duì)象組成的數(shù)組作為參數(shù),并返回一個(gè)新的promise對(duì)象。
當(dāng)數(shù)組中所有的對(duì)象都resolve時(shí),新對(duì)象狀態(tài)變?yōu)?b>fulfilled,所有對(duì)象的resolve的value依次添加組成一個(gè)新的數(shù)組,并以新的數(shù)組作為新對(duì)象resolve的value,例:
Promise.all([Promise.resolve(5), Promise.resolve(6), Promise.resolve(7)]).then(function(value){ console.log("fulfill", value) // fulfill [5, 6, 7] }, function(reason){ console.log("reject",reason) })
當(dāng)數(shù)組中有一個(gè)對(duì)象reject時(shí),新對(duì)象狀態(tài)變?yōu)?b>rejected,并以當(dāng)前對(duì)象reject的reason作為新對(duì)象reject的reason。
Promise.all([Promise.resolve(5), Promise.reject(new Error("error")), Promise.resolve(7), Promise.reject(new Error("other error")) ]).then(function(value){ console.log("fulfill", value) }, function(reason){ console.log("reject", reason) // reject Error: error(…) })
那當(dāng)數(shù)組中,傳入了非promise對(duì)象會(huì)如何呢?
Promise.all([Promise.resolve(5), 6, true, "test", undefined, null, {a:1}, function(){}, Promise.resolve(7) ]).then(function(value){ console.log("fulfill", value) // fulfill [5, 6, true, "test", undefined, null, Object, function, 7] }, function(reason){ console.log("reject", reason) })
我們發(fā)現(xiàn),當(dāng)傳入的值為數(shù)字、boolean、字符串、undefined、null、{a:1}、function(){}等非promise對(duì)象時(shí),會(huì)依次把它們添加到新對(duì)象resolve時(shí)傳遞的數(shù)組中。
那數(shù)組中的多個(gè)對(duì)象是同時(shí)調(diào)用,還是一個(gè)接一個(gè)的依次調(diào)用呢?我們?cè)倏磦€(gè)例子
function timeout(time) { return new Promise(function (resolve) { setTimeout(function () { resolve(time); }, time); }); } console.time("promise") Promise.all([ timeout(10), timeout(60), timeout(100) ]).then(function (values) { console.log(values); [10, 60, 100] console.timeEnd("promise"); // 107ms });
由此我們可以看出,傳入的多個(gè)對(duì)象幾乎是同時(shí)執(zhí)行的,因?yàn)榭偟臅r(shí)間略大于用時(shí)最長(zhǎng)的一個(gè)對(duì)象resolve的時(shí)間。
Promise.race() 它同樣接收一個(gè)promise對(duì)象組成的數(shù)組作為參數(shù),并返回一個(gè)新的promise對(duì)象。
與Promise.all()不同,它是在數(shù)組中有一個(gè)對(duì)象(最早改變狀態(tài))resolve或reject時(shí),就改變自身的狀態(tài),并執(zhí)行響應(yīng)的回調(diào)。
Promise.race([Promise.resolve(5), Promise.reject(new Error("error")), Promise.resolve(7)]).then(function(value){ console.log("fulfill", value) // fulfill 5 }, function(reason){ console.log("reject",reason) }) Promise.race([Promise.reject(new Error("error")), Promise.resolve(7)]).then(function(value){ console.log("fulfill", value) }, function(reason){ console.log("reject",reason) //reject Error: error(…) })
且當(dāng)數(shù)組中有非異步Promise對(duì)象或有數(shù)字、boolean、字符串、undefined、null、{a:1}、function(){}等非Promise對(duì)象時(shí),都會(huì)直接以該值resolve。
Promise.race([new Promise((resolve)=>{ setTimeout(()=>{ resolve(1) },100)}), Promise.resolve(5), "test", Promise.reject(new Error("error")), Promise.resolve(7)]).then(function(value){ console.log("fulfill", value) // fulfill 5 }, function(reason){ console.log("reject",reason) }) // fulfill 5
數(shù)組中第一個(gè)元素是異步的Promise,第二個(gè)是非異步Promise,會(huì)立即改變狀態(tài),所以新對(duì)象會(huì)立即改變狀態(tài)并把5傳遞給成功時(shí)的回調(diào)函數(shù)。
那么問(wèn)題又來(lái)了,既然數(shù)組中第一個(gè)元素成功或失敗就會(huì)改變新對(duì)象的狀態(tài),那數(shù)組中后面的對(duì)象是否會(huì)執(zhí)行呢?
function timeout(time) { return new Promise(function (resolve) { setTimeout(function () { console.log(time) resolve(time); }, time); }); } console.time("promise") Promise.race([ timeout(10), timeout(60), timeout(100) ]).then(function (values) { console.log(values); [10, 60, 100] console.timeEnd("promise"); // 107ms }); // 結(jié)果依次為 // 10 // 10 // promise: 11.1ms // 60 // 100
說(shuō)明即使新對(duì)象的狀態(tài)改變,數(shù)組中后面的promise對(duì)象還會(huì)執(zhí)行完畢,其實(shí)Promise.all()中即使前面reject了,所有的對(duì)象也都會(huì)執(zhí)行完畢。規(guī)范中,promise對(duì)象執(zhí)行是不可以中斷的。
補(bǔ)充promise對(duì)象即使立馬改變狀態(tài),它也是異步執(zhí)行的。如下所示:
Promise.resolve(5).then(function(value){ console.log("后打出來(lái)", value) }); console.log("先打出來(lái)") // 結(jié)果依次為 // 先打出來(lái) // 后打出來(lái) 5
但還有一個(gè)有意思的例子,如下:
setTimeout(function(){console.log(4)},0); new Promise(function(resolve){ console.log(1) for( var i=0 ; i<10000 ; i++ ){ i==9999 && resolve() } console.log(2) }).then(function(){ console.log(5) }); console.log(3);
結(jié)果是 1 2 3 5 4,命名4是先添加到異步隊(duì)列中的,為什么結(jié)果不是1 2 3 4 5呢?這個(gè)涉及到Event loop,后面我會(huì)多帶帶講一下。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/91230.html
摘要:我們稱為回調(diào)對(duì)象,它內(nèi)部會(huì)維護(hù)一個(gè)數(shù)組,我們可以向其中添加若干個(gè)回調(diào)函數(shù),然后在某一條件下觸發(fā)執(zhí)行。第一次之后,再次新的回調(diào)函數(shù)時(shí),自動(dòng)執(zhí)行回調(diào)。當(dāng)前面的回調(diào)函數(shù)返回時(shí),終止后面的回調(diào)繼續(xù)執(zhí)行。 最近懶癌發(fā)作,說(shuō)好的系列文章,寫了一半,一直懶得寫,今天補(bǔ)上一篇。 Deferred 我們?cè)谑褂胮romise對(duì)象時(shí),總會(huì)提到一個(gè)與它關(guān)系密切的對(duì)象——Deferred。其實(shí)Deferred沒(méi)...
摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書了入門,覺(jué)得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:先直接上源碼吧。阮一峰在基礎(chǔ)篇提到過(guò),阮一峰基礎(chǔ)介紹,返回的是一個(gè)新的實(shí)例,不是原來(lái)的那個(gè)實(shí)例。同理綁定和的指向。秒后,是為了讓在隊(duì)列的最后執(zhí)行。此時(shí)將中第一個(gè)回調(diào)函數(shù)執(zhí)行的賦值給了。這就驗(yàn)證了阮一峰在基礎(chǔ)介紹將的下面的代碼邏輯。 先直接上源碼吧。 if(!window.Promise) { function Promise(fn) { var self=thi...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁(yè)面加載發(fā)生了什么這是一篇開(kāi)發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...
摘要:記錄下我遇到的面試題,都有大佬分享過(guò),附上各個(gè)大佬的文章,總結(jié)出其中的主要思想即可。推薦黑金團(tuán)隊(duì)的文章前端緩存最佳實(shí)踐推薦名揚(yáng)的文章淺解強(qiáng)緩存和協(xié)商緩存狀態(tài)碼重點(diǎn)是等,要給面試官介紹清楚。前言 在這互聯(lián)網(wǎng)的寒冬臘月時(shí)期,雖說(shuō)過(guò)了金三銀四,但依舊在招人不斷。更偏向于招聘高級(jí)開(kāi)發(fā)工程師。本人在這期間求職,去了幾家創(chuàng)業(yè),小廠,大廠廝殺了一番,也得到了自己滿意的offer。 整理一下自己還記得的面試...
閱讀 2464·2021-09-28 09:36
閱讀 3607·2021-09-22 15:41
閱讀 4409·2021-09-04 16:45
閱讀 1991·2019-08-30 15:55
閱讀 2850·2019-08-30 13:49
閱讀 829·2019-08-29 16:34
閱讀 2377·2019-08-29 12:57
閱讀 1685·2019-08-26 18:42