您好,登錄后才能下訂單哦!
這篇文章主要介紹“Promise的原理和基礎(chǔ)用法介紹”,在日常操作中,相信很多人在Promise的原理和基礎(chǔ)用法介紹問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Promise的原理和基礎(chǔ)用法介紹”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
new Promise(function(resolve, reject) { //待處理的異步邏輯 //處理結(jié)束后,調(diào)用resolve或reject方法 }) |
新建一個(gè)
promise
很簡(jiǎn)單,只需要new
一個(gè)promise
對(duì)象即可。所以promise
本質(zhì)上就是一個(gè)函數(shù),它接受一個(gè)函數(shù)作為參數(shù),并且會(huì)返回promise
對(duì)象,這就給鏈?zhǔn)秸{(diào)用提供了基礎(chǔ)其實(shí)
Promise
函數(shù)的使命,就是構(gòu)建出它的實(shí)例,并且負(fù)責(zé)幫我們管理這些實(shí)例。而這些實(shí)例有以下三種狀態(tài):
pending
: 初始狀態(tài),位履行或拒絕
fulfilled
: 意味著操作成功完成
rejected
: 意味著操作失敗
pending
狀態(tài)的Promise
對(duì)象可能以fulfilled
狀態(tài)返回了一個(gè)值,也可能被某種理由(異常信息)拒絕(reject
)了。當(dāng)其中任一種情況出現(xiàn)時(shí),Promise
對(duì)象的then
方法綁定的處理方法(handlers)就會(huì)被調(diào)用,then方法分別指定了resolve
方法和reject
方法的回調(diào)函數(shù)
var promise = new Promise(function(resolve, reject) { if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // 如果調(diào)用了resolve方法,執(zhí)行此函數(shù) }, function(value) { // 如果調(diào)用了reject方法,執(zhí)行此函數(shù) }); |
上述代碼很清晰的展示了
promise
對(duì)象運(yùn)行的機(jī)制。下面再看一個(gè)示例:
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.> client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler( ) { if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; }); return promise; }; getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出錯(cuò)了', error); }); |
上面代碼中,
resolve
方法和reject
方法調(diào)用時(shí),都帶有參數(shù)。它們的參數(shù)會(huì)被傳遞給回調(diào)函數(shù)。reject
方法的參數(shù)通常是Error
對(duì)象的實(shí)例,而resolve
方法的參數(shù)除了正常的值以外,還可能是另一個(gè)Promise
實(shí)例,比如像下面這樣。
var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); }) |
上面代碼中,
p1
和p2
都是Promise
的實(shí)例,但是p2
的resolve
方法將p1
作為參數(shù),這時(shí)p1
的狀態(tài)就會(huì)傳遞給p2
。如果調(diào)用的時(shí)候,p1
的狀態(tài)是pending
,那么p2
的回調(diào)函數(shù)就會(huì)等待p1
的狀態(tài)改變;如果p1
的狀態(tài)已經(jīng)是fulfilled
或者rejected
,那么p2
的回調(diào)函數(shù)將會(huì)立刻執(zhí)行
Promise.prototype.catch
方法是Promise.prototype.then(null, rejection)
的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)
getJSON("/visa.json").then(function(result) { // some code }).catch(function(error) { // 處理前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤 console.log('出錯(cuò)啦!', error); }); |
Promise
對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說,錯(cuò)誤總是會(huì)被下一個(gè)catch
語句捕獲
getJSON("/visa.json").then(function(json) { return json.name; }).then(function(name) { // proceed }).catch(function(error) { //處理前面任一個(gè)then函數(shù)拋出的錯(cuò)誤 }); |
Promise.all方法
Promise.all
方法用于將多個(gè)Promise
實(shí)例,包裝成一個(gè)新的Promise
實(shí)例
var p = Promise.all([p1,p2,p3]); |
上面代碼中,Promise.all
方法接受一個(gè)數(shù)組作為參數(shù),p1
、p2
、p3
都是Promise
對(duì)象的實(shí)例。(Promise.all
方法的參數(shù)不一定是數(shù)組,但是必須具有iterator
接口,且返回的每個(gè)成員都是Promise
實(shí)例。)
p
的狀態(tài)由p1
、p2
、p3
決定,分成兩種情況
只有p1
、p2
、p3
的狀態(tài)都變成fulfilled
,p
的狀態(tài)才會(huì)變成fulfilled
,此時(shí)p1
、p2
、p3
的返回值組成一個(gè)數(shù)組,傳遞給p
的回調(diào)函數(shù)
只要p1
、p2
、p3
之中有一個(gè)被rejected
,p
的狀態(tài)就變成rejected
,此時(shí)第一個(gè)被reject
的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)
// 生成一個(gè)Promise對(duì)象的數(shù)組 var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON("/get/addr" + id + ".json"); }); Promise.all(promises).then(function(posts) { // ... }).catch(function(reason){ // ... }); |
Promise.race方法
Promise.race
方法同樣是將多個(gè)Promise
實(shí)例,包裝成一個(gè)新的Promise
實(shí)例。
var p = Promise.race([p1,p2,p3]); |
上面代碼中,只要
p1
、p2
、p3
之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的返回值
如果Promise.all
方法和Promise.race
方法的參數(shù),不是Promise
實(shí)例,就會(huì)先調(diào)用下面講到的Promise.resolve
方法,將參數(shù)轉(zhuǎn)為Promise
實(shí)例,再進(jìn)一步處理
Promise.resolve
有時(shí)需要將現(xiàn)有對(duì)象轉(zhuǎn)為
Promise
對(duì)象,Promise.resolve
方法就起到這個(gè)作用
var jsPromise = Promise.resolve($.ajax('/whatever.json')); |
上面代碼將
jQuery
生成deferred
對(duì)象,轉(zhuǎn)為一個(gè)新的ES6
的Promise
對(duì)象
如果Promise.resolve
方法的參數(shù),不是具有then
方法的對(duì)象(又稱thenable
對(duì)象),則返回一個(gè)新的Promise
對(duì)象,且它的狀態(tài)為fulfilled
。
var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello |
上面代碼生成一個(gè)新的Promise
對(duì)象的實(shí)例p
,它的狀態(tài)為fulfilled
,所以回調(diào)函數(shù)會(huì)立即執(zhí)行,Promise.resolve
方法的參數(shù)就是回調(diào)函數(shù)的參數(shù)
如果Promise.resolve
方法的參數(shù)是一個(gè)Promise
對(duì)象的實(shí)例,則會(huì)被原封不動(dòng)地返回
Promise.reject(reason)
方法也會(huì)返回一個(gè)新的Promise
實(shí)例,該實(shí)例的狀態(tài)為rejected
。Promise.reject
方法的參數(shù)reason
,會(huì)被傳遞給實(shí)例的回調(diào)函數(shù)
var p = Promise.reject('出錯(cuò)啦'); p.then(null, function (error){ console.log(error) }); // 出錯(cuò)了 |
function getDataAsync (url) { return new Promise((resolve, reject) => { setTimeout(() => { var res = { url: url, data: Math.random() } resolve(res) }, 1000) }) } |
async function getData ( ) { var res1 = await getDataAsync('/page/1?param=123') console.log(res1) var res2 = await getDataAsync(`/page/2?param=${res1.data}`) console.log(res2) var res3 = await getDataAsync(`/page/2?param=${res2.data}`) console.log(res3) } |
async/await
是基于Promise
的,因?yàn)槭褂?nbsp;async
修飾的方法最終返回一個(gè)Promise
, 實(shí)際上,async/await
可以看做是使用Generator
函數(shù)處理異步的語法糖,我們來看看如何使用Generator
函數(shù)處理異步
首先異步函數(shù)依然是:
function getDataAsync (url) { return new Promise((resolve, reject) => { setTimeout(() => { var res = { url: url, data: Math.random() } resolve(res) }, 1000) }) } |
使用
Generator
函數(shù)可以這樣寫
function * getData ( ) { var res1 = yield getDataAsync('/page/1?param=123') console.log(res1) var res2 = yield getDataAsync(`/page/2?param=${res1.data}`) console.log(res2) var res3 = yield getDataAsync(`/page/2?param=${res2.data}`) console.log(res3)) } |
然后我們這樣逐步執(zhí)行
var g = getData() g.next().value.then(res1 => { g.next(res1).value.then(res2 => { g.next(res2).value.then(() => { g.next() }) }) }) |
上面的代碼,我們逐步調(diào)用遍歷器的
next()
方法,由于每一個(gè)next()
方法返回值的value
屬性為一個(gè)Promise
對(duì)象,所以我們?yōu)槠涮砑?nbsp;then
方法, 在then
方法里面接著運(yùn)行next
方法挪移遍歷器指針,直到Generator
函數(shù)運(yùn)行完成,實(shí)際上,這個(gè)過程我們不必手動(dòng)完成,可以封裝成一個(gè)簡(jiǎn)單的執(zhí)行器
function run (gen) { var g = gen() function next (data) { var res = g.next(data) if (res.done) return res.value res.value.then((data) => { next(data) }) } next() } |
run
方法用來自動(dòng)運(yùn)行異步的Generator
函數(shù),其實(shí)就是一個(gè)遞歸的過程調(diào)用的過程。這樣我們就不必手動(dòng)執(zhí)行Generator
函數(shù)了。 有了run
方法,我們只需要這樣運(yùn)行 getData 方法
run(getData) |
這樣,我們就可以把異步操作封裝到
Generator
函數(shù)內(nèi)部,使用run
方法作為Generator
函數(shù)的自執(zhí)行器,來處理異步。其實(shí)我們不難發(fā)現(xiàn),async/await
方法相比于Generator
處理異步的方式,有很多相似的地方,只不過async/await
在語義化方面更加明顯,同時(shí)async/await
不需要我們手寫執(zhí)行器,其內(nèi)部已經(jīng)幫我們封裝好了,這就是為什么說async/await
是Generator
函數(shù)處理異步的語法糖了
Promise
規(guī)范有很多,如Promise/A
,Promise/B
,Promise/D
以及 Promise/A
的升級(jí)版 Promise/A+
。ES6
中采用了 Promise/A+
規(guī)范
中文版規(guī)范: Promises/A+規(guī)范(中文)
Promise標(biāo)準(zhǔn)解讀
一個(gè)promise
的當(dāng)前狀態(tài)只能是pending
、fulfilled
和rejected
三種之一。狀態(tài)改變只能是pending
到fulfilled
或者pending
到rejected
。狀態(tài)改變不可逆
promise
的then
方法接收兩個(gè)可選參數(shù),表示該promise
狀態(tài)改變時(shí)的回調(diào)(promise.then(onFulfilled, onRejected)
)。then
方法返回一個(gè)promise
。then
方法可以被同一個(gè) promise
調(diào)用多次
構(gòu)造函數(shù)
function Promise(resolver) {} |
原型方法
Promise.prototype.then = function( ) {} Promise.prototype.catch = function( ) {} |
靜態(tài)方法
Promise.resolve = function( ) {} Promise.reject = function( ) {} Promise.all = function( ) {} Promise.race = function( ) {} |
function Promise(fn) { var value = null, callbacks = []; //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào) this.then = function (onFulfilled) { callbacks.push(onFulfilled); }; function resolve(value) { callbacks.forEach(function (callback) { callback(value); }); } fn(resolve); } |
大致的邏輯是這樣的
調(diào)用then
方法,將想要在Promise
異步操作成功時(shí)執(zhí)行的回調(diào)放入callbacks
隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù),可以向觀察者模式方向思考
創(chuàng)建Promise
實(shí)例時(shí)傳入的函數(shù)會(huì)被賦予一個(gè)函數(shù)類型的參數(shù),即resolve
,它接收一個(gè)參數(shù)value
,代表異步操作返回的結(jié)果,當(dāng)一步操作執(zhí)行成功后,用戶會(huì)調(diào)用resolve
方法,這時(shí)候其實(shí)真正執(zhí)行的操作是將callbacks
隊(duì)列中的回調(diào)一一執(zhí)行
//例1 function getUserId( ) { return new Promise(function(resolve) { //異步請(qǐng)求 http.get(url, function(results) { resolve(results.id) }) }) } getUserId().then(function(id) { //一些處理 }) |
// 結(jié)合例子1分析 // fn 就是getUserId函數(shù) function Promise(fn) { var value = null, callbacks = []; //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào) // 當(dāng)用戶調(diào)用getUserId().then的時(shí)候開始注冊(cè)傳進(jìn)來的回調(diào)函數(shù) // onFulfilled就是例子中的function(id){} // 把then的回調(diào)函數(shù)收集起來 在resolve的時(shí)候調(diào)用 this.then = function (onFulfilled) { callbacks.push(onFulfilled); }; // value是fn函數(shù)執(zhí)行后返回的值 function resolve(value) { // callbacks是傳給then的回調(diào)函數(shù)就是例子中的function(id){} // 遍歷用戶通過then傳遞進(jìn)來的回調(diào)函數(shù)把resolve成功的結(jié)果返回給then調(diào)用即then(function(data){ console.log(data) }) 這里的data就是通過這里調(diào)用返回 callbacks.forEach(function (callback) { callback(value); }); } //執(zhí)行fn函數(shù)即getUserId()并且傳入函數(shù)參數(shù)resolve 當(dāng)fn執(zhí)行完成返回的值傳遞給resolve函數(shù) fn(resolve); } |
結(jié)合例1中的代碼來看,首先
new Promise
時(shí),傳給promise
的函數(shù)發(fā)送異步請(qǐng)求,接著調(diào)用promise
對(duì)象的then
屬性,注冊(cè)請(qǐng)求成功的回調(diào)函數(shù),然后當(dāng)異步請(qǐng)求發(fā)送成功時(shí),調(diào)用resolve(results.id)
方法, 該方法執(zhí)行then
方法注冊(cè)的回調(diào)數(shù)組
then
方法應(yīng)該能夠鏈?zhǔn)秸{(diào)用,但是上面的最基礎(chǔ)簡(jiǎn)單的版本顯然無法支持鏈?zhǔn)秸{(diào)用。想讓then
方法支持鏈?zhǔn)秸{(diào)用,其實(shí)也是很簡(jiǎn)單的
this.then = function (onFulfilled) { callbacks.push(onFulfilled); return this; }; |
只要簡(jiǎn)單一句話就可以實(shí)現(xiàn)類似下面的鏈?zhǔn)秸{(diào)用
// 例2 getUserId().then(function (id) { // 一些處理 }).then(function (id) { // 一些處理 }); |
上述代碼可能還存在一個(gè)問題:如果在
then
方法注冊(cè)回調(diào)之前,resolve
函數(shù)就執(zhí)行了,怎么辦?比如promise
內(nèi)部的函數(shù)是同步函數(shù)
// 例3 function getUserId( ) { return new Promise(function (resolve) { resolve(9876); }); } getUserId().then(function (id) { // 一些處理 }); |
這顯然是不允許的,
Promises/A+
規(guī)范明確要求回調(diào)需要通過異步方式執(zhí)行,用以保證一致可靠的執(zhí)行順序。因此我們要加入一些處理,保證在resolve
執(zhí)行之前,then
方法已經(jīng)注冊(cè)完所有的回調(diào)。我們可以這樣改造下resolve
函數(shù):
function resolve(value) { setTimeout(function( ) { callbacks.forEach(function (callback) { callback(value); }); }, 0) } |
上述代碼的思路也很簡(jiǎn)單,就是通過
setTimeout
機(jī)制,將resolve
中執(zhí)行回調(diào)的邏輯放置到JS
任務(wù)隊(duì)列末尾,以保證在resolve
執(zhí)行時(shí),then
方法的回調(diào)函數(shù)已經(jīng)注冊(cè)完成
但是,這樣好像還存在一個(gè)問題,可以細(xì)想一下:如果Promise
異步操作已經(jīng)成功,這時(shí),在異步操作成功之前注冊(cè)的回調(diào)都會(huì)執(zhí)行,但是在Promise
異步操作成功這之后調(diào)用的then
注冊(cè)的回調(diào)就再也不會(huì)執(zhí)行了,這顯然不是我們想要的
我們必須加入狀態(tài)機(jī)制,也就是大家熟知的pending
、fulfilled
、rejected
Promises/A+
規(guī)范中的2.1 Promise States
中明確規(guī)定了,pending
可以轉(zhuǎn)化為fulfilled
或rejected
并且只能轉(zhuǎn)化一次,也就是說如果pending
轉(zhuǎn)化到fulfilled
狀態(tài),那么就不能再轉(zhuǎn)化到rejected
。并且fulfilled
和rejected
狀態(tài)只能由pending
轉(zhuǎn)化而來,兩者之間不能互相轉(zhuǎn)換
//改進(jìn)后的代碼是這樣的: function Promise(fn) { var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled) { if (state === 'pending') { callbacks.push(onFulfilled); return this; } onFulfilled(value); return this; }; function resolve(newValue) { value = newValue; state = 'fulfilled'; setTimeout(function ( ) { callbacks.forEach(function (callback) { callback(value); }); }, 0); } fn(resolve); } |
上述代碼的思路是這樣的:
resolve
執(zhí)行時(shí),會(huì)將狀態(tài)設(shè)置為fulfilled
,在此之后調(diào)用then
添加的新回調(diào),都會(huì)立即執(zhí)行
如果用戶在
then
函數(shù)里面注冊(cè)的仍然是一個(gè)Promise
,該如何解決?比如下面的例4
// 例4 getUserId() .then(getUserJobById) .then(function (job) { // 對(duì)job的處理 }); function getUserJobById(id) { return new Promise(function (resolve) { http.get(baseUrl + id, function(job) { resolve(job); }); }); } |
這種場(chǎng)景相信用過promise
的人都知道會(huì)有很多,那么類似這種就是所謂的鏈?zhǔn)?code>Promise
鏈?zhǔn)?code>Promise是指在當(dāng)前promise
達(dá)到fulfilled
狀態(tài)后,即開始進(jìn)行下一個(gè)promise
(后鄰promise
)。那么我們?nèi)绾毋暯赢?dāng)前promise
和后鄰promise
呢?(這是這里的難點(diǎn)
只要在then
方法里面return
一個(gè)promise
就好啦。Promises/A+
規(guī)范中的2.2.7
就是這樣
下面來看看這段暗藏玄機(jī)的
then
方法和resolve
方法改造代碼
function Promise(fn) { var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled) { return new Promise(function (resolve) { handle({ onFulfilled: onFulfilled || null, resolve: resolve }); }); }; function handle(callback) { if (state === 'pending') { callbacks.push(callback); return; } //如果then中沒有傳遞任何東西 if(!callback.onFulfilled) { callback.resolve(value); return; } var ret = callback.onFulfilled(value); callback.resolve(ret); } function resolve(newValue) { if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { then.call(newValue, resolve); return; } } state = 'fulfilled'; value = newValue; setTimeout(function ( ) { callbacks.forEach(function (callback) { handle(callback); }); }, 0); } fn(resolve); } |
我們結(jié)合例4的代碼,分析下上面的代碼邏輯,為了方便閱讀,我把例4的代碼貼在這里
// 例4 getUserId() .then(getUserJobById) .then(function (job) { // 對(duì)job的處理 }); function getUserJobById(id) { return new Promise(function (resolve) { http.get(baseUrl + id, function(job) { resolve(job); }); }); } |
then
方法中,創(chuàng)建并返回了新的Promise
實(shí)例,這是串行Promis
e的基礎(chǔ),并且支持鏈?zhǔn)秸{(diào)用
handle
方法是promise
內(nèi)部的方法。then
方法傳入的形參onFulfilled
以及創(chuàng)建新Promise
實(shí)例時(shí)傳入的resolve
均被push
到當(dāng)前promise
的callbacks
隊(duì)列中,這是銜接當(dāng)前promise
和后鄰promise
的關(guān)鍵所在
getUserId
生成的promise
(簡(jiǎn)稱getUserId promise
)異步操作成功,執(zhí)行其內(nèi)部方法resolve
,傳入的參數(shù)正是異步操作的結(jié)果id
調(diào)用handle
方法處理callbacks
隊(duì)列中的回調(diào):getUserJobById
方法,生成新的promise
(getUserJobById promise
)
執(zhí)行之前由getUserId promise
的then
方法生成的新promise
(稱為bridge promise
)的resolve
方法,傳入?yún)?shù)為getUserJobById promise
。這種情況下,會(huì)將該resolve
方法傳入getUserJobById promise
的then
方法中,并直接返回
在getUserJobById promise
異步操作成功時(shí),執(zhí)行其callbacks
中的回調(diào):getUserId bridge promise
中的resolve
方法
最后執(zhí)行getUserId bridge promise
的后鄰promise
的callbacks
中的回調(diào)
在異步操作失敗時(shí),標(biāo)記其狀態(tài)為
rejected
,并執(zhí)行注冊(cè)的失敗回調(diào)
//例5 function getUserId( ) { return new Promise(function(resolve) { //異步請(qǐng)求 http.get(url, function(error, results) { if (error) { reject(error); } resolve(results.id) }) }) } getUserId().then(function(id) { //一些處理 }, function(error) { console.log(error) }) |
有了之前處理
fulfilled
狀態(tài)的經(jīng)驗(yàn),支持錯(cuò)誤處理變得很容易,只需要在注冊(cè)回調(diào)、處理狀態(tài)變更上都要加入新的邏輯
function Promise(fn) { var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled, onRejected) { return new Promise(function (resolve, reject) { handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve: resolve, reject: reject }); }); }; function handle(callback) { if (state === 'pending') { callbacks.push(callback); return; } var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected, ret; if (cb === null) { cb = state === 'fulfilled' ? callback.resolve : callback.reject; cb(value); return; } ret = cb(value); callback.resolve(ret); } function resolve(newValue) { if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { then.call(newValue, resolve, reject); return; } } state = 'fulfilled'; value = newValue; execute(); } function reject(reason) { state = 'rejected'; value = reason; execute(); } function execute( ) { setTimeout(function ( ) { callbacks.forEach(function (callback) { handle(callback); }); }, 0); } fn(resolve, reject); } |
上述代碼增加了新的
reject
方法,供異步操作失敗時(shí)調(diào)用,同時(shí)抽出了resolve
和reject
共用的部分,形成execute
方法
錯(cuò)誤冒泡是上述代碼已經(jīng)支持,且非常實(shí)用的一個(gè)特性。在handle
中發(fā)現(xiàn)沒有指定異步操作失敗的回調(diào)時(shí),會(huì)直接將bridge promise
(then
函數(shù)返回的promise
,后同)設(shè)為rejected
狀態(tài),如此達(dá)成執(zhí)行后續(xù)失敗回調(diào)的效果。這有利于簡(jiǎn)化串行Promise的失敗處理成本,因?yàn)橐唤M異步操作往往會(huì)對(duì)應(yīng)一個(gè)實(shí)際功能,失敗處理方法通常是一致的
//例6 getUserId() .then(getUserJobById) .then(function (job) { // 處理job }, function (error) { // getUserId或者getUerJobById時(shí)出現(xiàn)的錯(cuò)誤 console.log(error); }); |
如果在執(zhí)行成功回調(diào)、失敗回調(diào)時(shí)代碼出錯(cuò)怎么辦?對(duì)于這類異常,可以使用
try-catch
捕獲錯(cuò)誤,并將bridge promise
設(shè)為rejected
狀態(tài)。handle
方法改造如下
function handle(callback) { if (state === 'pending') { callbacks.push(callback); return; } var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected, ret; if (cb === null) { cb = state === 'fulfilled' ? callback.resolve : callback.reject; cb(value); return; } try { ret = cb(value); callback.resolve(ret); } catch (e) { callback.reject(e); } } |
如果在異步操作中,多次執(zhí)行
resolve
或者reject
會(huì)重復(fù)處理后續(xù)回調(diào),可以通過內(nèi)置一個(gè)標(biāo)志位解決
// 三種狀態(tài) const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一個(gè)函數(shù)參數(shù),該函數(shù)會(huì)立即執(zhí)行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回調(diào),只有當(dāng) promise // 狀態(tài)為 pending 時(shí)才會(huì)緩存,并且每個(gè)實(shí)例至多緩存一個(gè) _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 如果 value 是個(gè) Promise,遞歸執(zhí)行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解決以下問題 // new Promise(() => throw Error('error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 規(guī)范 2.2.7,then 必須返回一個(gè)新的 promise var promise2; // 規(guī)范 2.2.onResolved 和 onRejected 都為可選參數(shù) // 如果類型不是函數(shù)需要忽略,同時(shí)也實(shí)現(xiàn)了透?jìng)? // Promise.resolve(4).then().then((value) => console.log(value)) typeof 'function' ? onResolved : v => v; typeof 'function' ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 規(guī)范 2.2.4,保證 onFulfilled,onRjected 異步執(zhí)行 // 所以用了 setTimeout 包裹下 setTimeout(function ( ) { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === REJECTED) { return (promise2 = new MyPromise(function (resolve, reject) { setTimeout(function ( ) { // 異步執(zhí)行onRejected try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === PENDING) { return (promise2 = new MyPromise(function (resolve, reject) { self.resolvedCallbacks.push(function ( ) { // 考慮到可能會(huì)有報(bào)錯(cuò),所以使用 try/catch 包裹 try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); self.rejectedCallbacks.push(function ( ) { try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); })); } }; // 規(guī)范 2.3 function resolutionProcedure(promise2, x, resolve, reject) { // 規(guī)范 2.3.1,x 不能和 promise2 相同,避免循環(huán)引用 if (promise2 === x) { return reject(new TypeError("Error")); } // 規(guī)范 2.3.2 // 如果 x 為 Promise,狀態(tài)為 pending 需要繼續(xù)等待否則執(zhí)行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(function (value) { // 再次調(diào)用該函數(shù)是為了確認(rèn) x resolve 的 // 參數(shù)是什么類型,如果是基本類型就再次 resolve // 把值傳給下個(gè) then resolutionProcedure(promise2, value, resolve, reject); }, reject); } else { x.then(resolve, reject); } return; } // 規(guī)范 2.3.3.3.3 // reject 或者 resolve 其中一個(gè)執(zhí)行過得話,忽略其他的 let called = false; // 規(guī)范 2.3.3,判斷 x 是否為對(duì)象或者函數(shù) if (x !== null && (typeof x === "object" || typeof x === "function")) { // 規(guī)范 2.3.3.2,如果不能取出 then,就 reject try { // 規(guī)范 2.3.3.1 let then = x.then; // 如果 then 是函數(shù),調(diào)用 x.then if (typeof then === "function") { // 規(guī)范 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 規(guī)范 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 規(guī)范 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 規(guī)范 2.3.4,x 為基本類型 resolve(x); } } |
這里一定要注意的點(diǎn)是:promise
里面的then
函數(shù)僅僅是注冊(cè)了后續(xù)需要執(zhí)行的代碼,真正的執(zhí)行是在resolve
方法里面執(zhí)行的,理清了這層,再來分析源碼會(huì)省力的多
現(xiàn)在回顧下
Promise
的實(shí)現(xiàn)過程,其主要使用了設(shè)計(jì)模式中的觀察者模式
通過Promise.prototype.then
和Promise.prototype.catch
方法將觀察者方法注冊(cè)到被觀察者Promise
對(duì)象中,同時(shí)返回一個(gè)新的Promise
對(duì)象,以便可以鏈?zhǔn)秸{(diào)用
被觀察者管理內(nèi)部pending
、fulfilled
和rejected
的狀態(tài)轉(zhuǎn)變,同時(shí)通過構(gòu)造函數(shù)中傳遞的resolve
和reject
方法以主動(dòng)觸發(fā)狀態(tài)轉(zhuǎn)變和通知觀察者
到此,關(guān)于“Promise的原理和基礎(chǔ)用法介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。