理解JavaScript中Promise的使用
Javascript 采用回調(diào)函數(shù)(callback)來(lái)處理異步編程。從同步編程到異步回調(diào)編程有一個(gè)適應(yīng)的過(guò)程,但是如果出現(xiàn)多層回調(diào)嵌套,也就是我們常說(shuō)的厄運(yùn)的回調(diào)金字塔(Pyramid of Doom),絕對(duì)是一種糟糕的編程體驗(yàn)。于是便有了 CommonJS 的 Promises/A 規(guī)范,用于解決回調(diào)金字塔問(wèn)題。本文先介紹 Promises 相關(guān)規(guī)范,然后再通過(guò)解讀一個(gè)迷你的 Promises 以加深理解。
什么是 Promise
一個(gè) Promise 對(duì)象代表一個(gè)目前還不可用,但是在未來(lái)的某個(gè)時(shí)間點(diǎn)可以被解析的值。它允許你以一種同步的方式編寫異步代碼。例如,如果你想要使用 Promise API 異步調(diào)用一個(gè)遠(yuǎn)程的服務(wù)器,你需要?jiǎng)?chuàng)建一個(gè)代表數(shù)據(jù)將會(huì)在未來(lái)由 Web 服務(wù)返回的 Promise 對(duì)象。唯一的問(wèn)題是目前數(shù)據(jù)還不可用。當(dāng)請(qǐng)求完成并從服務(wù)器返回時(shí)數(shù)據(jù)將變?yōu)榭捎脭?shù)據(jù)。在此期間,Promise 對(duì)象將扮演一個(gè)真實(shí)數(shù)據(jù)的代理角色。接下來(lái),你可以在 Promise 對(duì)象上綁定一個(gè)回調(diào)函數(shù),一旦真實(shí)數(shù)據(jù)變得可用這個(gè)回調(diào)函數(shù)將會(huì)被調(diào)用。
Promise 對(duì)象曾經(jīng)以多種形式存在于許多語(yǔ)言中。
去除厄運(yùn)的回調(diào)金字塔(Pyramid of Doom)
Javascript 中最常見(jiàn)的反模式做法是回調(diào)內(nèi)部再嵌套回調(diào)。
// 回調(diào)金字塔
asyncOperation(function(data){
// 處理 `data`
anotherAsync(function(data2){
// 處理 `data2`
yetAnotherAsync(function(){
// 完成
});
});
});
引入 Promises 之后的代碼
promiseSomething()
.then(function(data){
// 處理 `data`
return anotherAsync();
})
.then(function(data2){
// 處理 `data2`
return yetAnotherAsync();
})
.then(function(){
// 完成
});
Promises 將嵌套的 callback,改造成一系列的.then的連綴調(diào)用,去除了層層縮進(jìn)的糟糕代碼風(fēng)格。Promises 不是一種解決具體問(wèn)題的算法,而已一種更好的代碼組織模式。接受新的組織模式同時(shí),也逐漸以全新的視角來(lái)理解異步調(diào)用。
各個(gè)語(yǔ)言平臺(tái)都有相應(yīng)的 Promise 實(shí)現(xiàn)
- Java's java.util.concurrent.Future
- Python's Twisted deferreds and PEP-3148 futures
- F#'s Async
- .Net's Task
- C++ 11's std::future
- Dart's Future
- Javascript's Promises/A/B/D/A+
下面我來(lái)相信了解一下 javascript 語(yǔ)言環(huán)境下各個(gè)規(guī)范的一些細(xì)節(jié)。
Promises/A 規(guī)范
promise 表示一個(gè)最終值,該值由一個(gè)操作完成時(shí)返回。
- promise 有三種狀態(tài):**未完成** (unfulfilled),**完成** (fulfilled) 和**失敗** (failed)。
- promise 的狀態(tài)只能由**未完成**轉(zhuǎn)換成完成,或者**未完成**轉(zhuǎn)換成**失敗** 。
- promise 的狀態(tài)轉(zhuǎn)換只發(fā)生一次。
promise 有一個(gè) then 方法,then 方法可以接受 3 個(gè)函數(shù)作為參數(shù)。前兩個(gè)函數(shù)對(duì)應(yīng) promise 的兩種狀態(tài) fulfilled 和 rejected 的回調(diào)函數(shù)。第三個(gè)函數(shù)用于處理進(jìn)度信息(對(duì)進(jìn)度回調(diào)的支持是可選的)。
promiseSomething().then(function(fulfilled){
//當(dāng)promise狀態(tài)變成fulfilled時(shí),調(diào)用此函數(shù)
},function(rejected){
//當(dāng)promise狀態(tài)變成rejected時(shí),調(diào)用此函數(shù)
},function(progress){
//當(dāng)返回進(jìn)度信息時(shí),調(diào)用此函數(shù)
});
如果 promise 支持如下連個(gè)附加方法,稱之為可交互的 promise
- get(propertyName)
獲得當(dāng)前 promise 最終值上的一個(gè)屬性,返回值是一個(gè)新的 promise。
- call(functionName, arg1, arg2, ...)
調(diào)用當(dāng)然 promise 最終值上的一個(gè)方法,返回值也是一個(gè)新的promise。
Promises/B 規(guī)范
在 Promises/A 的基礎(chǔ)上,Promises/B 定義了一組 promise 模塊需要實(shí)現(xiàn)的 API
when(value, callback, errback_opt)
如果 value 不是一個(gè) promise ,那么下一事件循環(huán)callback會(huì)被調(diào)用,value 作為 callback 的傳入值。如果 value 是一個(gè) promise,promise 的狀態(tài)已經(jīng)完成或者變成完成時(shí),那么下一事件循環(huán) callback 會(huì)被調(diào)用,resolve 的值會(huì)被傳入 callback;promise 的狀態(tài)已經(jīng)失敗或者變成失敗時(shí),那么下一事件循環(huán) errback 會(huì)被調(diào)用,reason 會(huì)作為失敗的理由傳入 errback。
asap(value, callback, errback_opt)
與 when 最大的區(qū)別,如果 value 不是一個(gè) promise,會(huì)被立即執(zhí)行,不會(huì)等到下一事件循環(huán)。
enqueue(task Function)
盡可能快地在接下來(lái)的事件循環(huán)調(diào)用 task 方法。
get(object, name)
返回一個(gè)獲得對(duì)象屬性的 promise。
post(object, name, args)
返回一個(gè)調(diào)用對(duì)象方法的 promise。
put(object, name, value)
返回一個(gè)修改對(duì)象屬性的 promise。
del(object, name)
返回一個(gè)刪除對(duì)象屬性的 promise。
makePromise(descriptor Object, fallback Function)
返回一個(gè) promise 對(duì)象,該對(duì)象必須是一個(gè)可調(diào)用的函數(shù),也可能是可被實(shí)例化的構(gòu)造函數(shù)。
- 第一個(gè)參數(shù)接受一個(gè)描述對(duì)象,該對(duì)象結(jié)構(gòu)如下,
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, }
上面每一個(gè)注冊(cè)的 handle 都返回一個(gè) resolved value或者 promise。
- 第二個(gè)參數(shù)接受一個(gè) fallback(message,...args) 函數(shù),當(dāng)沒(méi)有 promise 對(duì)象沒(méi)有找到對(duì)應(yīng)的 handle 時(shí)該函數(shù)會(huì)被觸發(fā),返回一個(gè) resolved value 或者 promise。
defer()
返回一個(gè)對(duì)象,該對(duì)象包含一個(gè) resolve(value) 方法和一個(gè) promise 屬性。
當(dāng) resolve(value) 方法被第一次調(diào)用時(shí),promise 屬性的狀態(tài)變成 完成,所有之前或之后觀察該 promise 的 promise 的狀態(tài)都被轉(zhuǎn)變成 完成。value 參數(shù)如果不是一個(gè) promise ,會(huì)被包裝成一個(gè) promise 的 ref。resolve 方法會(huì)忽略之后的所有調(diào)用。
reject(reason String)
返回一個(gè)被標(biāo)記為 失敗 的 promise。
一個(gè)失敗的 promise 上被調(diào)用 when(message) 方法時(shí),會(huì)采用如下兩種方法之一
1. 如果存在 errback,errback 會(huì)以 reason 作為參數(shù)被調(diào)用。when方法會(huì)將 errback 的返回值返回。
2. 如果不存在 errback,when 方法返回一個(gè)新的 reject 狀態(tài)的promise 對(duì)象,以同一 reason 作為參數(shù)。
ref(value)
如果 value 是 promise 對(duì)象,返回 value 本身。否則,返回一個(gè)resolved 的 promise,攜帶如下 handle。
1. when(errback),忽略 errback,返回 resolved 值
2. get(name),返回 resolved 值的對(duì)應(yīng)屬性。
3. put(name, value) ,設(shè)置 resolved 值的對(duì)應(yīng)屬性。
4. del(name),刪除 resolved 值的對(duì)應(yīng)屬性。
5. post(name, args), 調(diào)用 resolved 值的對(duì)應(yīng)方法。
6. 其他所有的調(diào)用都返回一個(gè) reject,并攜帶 "Promise does not handle NAME" 的理由。
isPromise(value) Boolean
判斷一個(gè)對(duì)象是否是 promise
method(name String)
獲得一個(gè)返回 name 對(duì)應(yīng)方法的 promise。返回值是 "get", "put", "del" 和 "post" 對(duì)應(yīng)的方法,但是會(huì)在下一事件循環(huán)返回。
Promises/D 規(guī)范
為了增加不同 promise 實(shí)現(xiàn)之間的可互操作性,Promises/D 規(guī)范對(duì)promise 對(duì)象和 Promises/B 規(guī)范做了進(jìn)一步的約定。以達(dá)到鴨子類型的效果(Duck-type Promise)。
簡(jiǎn)單來(lái)說(shuō)Promises/D 規(guī)范,做了兩件事情,
1、如何判斷一個(gè)對(duì)象是 Promise 類型。
2、對(duì) Promises/B 規(guī)范進(jìn)行細(xì)節(jié)補(bǔ)充。
甄別一個(gè) Promise 對(duì)象
Promise 對(duì)象必須是實(shí)現(xiàn) promiseSend 方法。
1. 在 promise 庫(kù)上下文中,如果對(duì)象包含 promiseSend 方法就可以甄別為promise 對(duì)象
2. promiseSend 方法必須接受一個(gè)操作名稱,作為第一個(gè)參數(shù)
3. 操作名稱是一個(gè)可擴(kuò)展的集合,下面是一些保留名稱
1. when,此時(shí)第三個(gè)參數(shù)必須是 rejection 回調(diào)。
1. rejection回調(diào)必須接受一個(gè) rejection 原因(可以是任何值)作為第一個(gè)參數(shù)
2. get,此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型)
3. put,此時(shí)第三個(gè)參數(shù)為屬性名(字符串類型),第四個(gè)參數(shù)為新屬性值。
4. del,此時(shí)第三個(gè)參數(shù)為屬性名
5. post,此時(shí)第三個(gè)參數(shù)為方法的屬性名,接下來(lái)的變參為方法的調(diào)用參數(shù)
6. isDef
4. promiseSend方法的第二個(gè)參數(shù)為 resolver 方法
5. promiseSend方法可能接受變參
6. promiseSend方法必須返回undefined
對(duì) Promises/B 規(guī)范的補(bǔ)充
Promises/D 規(guī)范中對(duì) Promises/B 規(guī)范中定義的ref、reject、def、defer方法做了進(jìn)一步細(xì)致的約束,此處略去這些細(xì)節(jié)。
Promises/A+ 規(guī)范
前面提到的 Promises/A/B/D 規(guī)范都是有CommonJS組織提出的,Promises/A+是有一個(gè)自稱為Promises/A+ 組織發(fā)布的,該規(guī)范是以Promises/A作為基礎(chǔ)進(jìn)行補(bǔ)充和修訂,旨在提高promise實(shí)現(xiàn)之間的可互操作性。
Promises/A+ 對(duì).then方法進(jìn)行細(xì)致的補(bǔ)充,定義了細(xì)致的Promise Resolution Procedure流程,并且將.then方法作為promise的對(duì)象甄別方法。
此外,Promises/A+ 還提供了兼容性測(cè)試工具,以確定各個(gè)實(shí)現(xiàn)的兼容性。
實(shí)現(xiàn)一個(gè)迷你版本的Promise
上面扯了這么多規(guī)范,現(xiàn)在我們看看如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單而短小的Promise。
1、狀態(tài)機(jī)
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value or error once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers attached by calling .then or .done
var handlers = [];
}
2、狀態(tài)變遷
僅支持兩種狀態(tài)變遷,fulfill和reject
// ...
function Promise() {
// ...
function fulfill(result) {
state = FULFILLED;
value = result;
}
function reject(error) {
state = REJECTED;
value = error;
}
}
fulfill和reject方法較為底層,通常更高級(jí)的resolve方法開(kāi)放給外部。
// ...
function Promise() {
// ...
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result);
} catch (e) {
reject(e);
}
}
}
resolve方法可以接受一個(gè)普通值或者另一個(gè)promise作為參數(shù),如果接受一個(gè)promise作為參數(shù),等待其完成。promise不允許被另一個(gè)promise fulfill,所以需要開(kāi)放resolve方法。resolve方法依賴一些幫助方法定義如下:
/**
* Check if a value is a Promise and, if it is,
* return the `then` method of that promise.
*
* @param {Promise|Any} value
* @return {Function|Null}
*/
function getThen(value) {
var t = typeof value;
if (value && (t === 'object' || t === 'function')) {
var then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*
* @param {Function} fn A resolver function that may not be trusted
* @param {Function} onFulfilled
* @param {Function} onRejected
*/
function doResolve(fn, onFulfilled, onRejected) {
var done = false;
try {
fn(function (value) {
if (done) return
done = true
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
這里resolve和doResolve之間的遞歸很巧妙,用來(lái)處理promise的層層嵌套(promise的value是一個(gè)promise)。
構(gòu)造器
// ...
function Promise(fn) {
// ...
doResolve(fn, resolve, reject);
}
.done方法
// ...
function Promise(fn) {
// ...
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else {
if (state === FULFILLED &&
typeof handler.onFulfilled === 'function') {
handler.onFulfilled(value);
}
if (state === REJECTED &&
typeof handler.onRejected === 'function') {
handler.onRejected(value);
}
}
}
this.done = function (onFulfilled, onRejected) {
// ensure we are always asynchronous
setTimeout(function () {
handle({
onFulfilled: onFulfilled,
onRejected: onRejected
});
}, 0);
}
// ...
}
.then方法
// ...
function Promise(fn) {
// ...
this.then = function (onFulfilled, onRejected) {
var self = this;
return new Promise(function (resolve, reject) {
return self.done(function (result) {
if (typeof onFulfilled === 'function') {
try {
return resolve(onFulfilled(result));
} catch (ex) {
return reject(ex);
}
} else {
return resolve(result);
}
}, function (error) {
if (typeof onRejected === 'function') {
try {
return resolve(onRejected(error));
} catch (ex) {
return reject(ex);
}
} else {
return reject(error);
}
});
});
}
// ...
}
$.promise
jQuery 1.8 之前的版本,jQuery的 then 方法只是一種可以同時(shí)調(diào)用 done 、fail 和 progress 這三種回調(diào)的速寫方法,而 Promises/A 規(guī)范的 then 在行為上更像是 jQuery 的 pipe。 jQuery 1.8 修正了這個(gè)問(wèn)題,使 then 成為 pipe 的同義詞。不過(guò),由于向后兼容的問(wèn)題,jQuery 的 Promise 再如何對(duì) Promises/A 示好也不太會(huì)招人待見(jiàn)。
此外,在 Promises/A 規(guī)范中,由 then 方法生成的 Promise 對(duì)象是已執(zhí)行還是已拒絕,取決于由 then 方法調(diào)用的那個(gè)回調(diào)是返回值還是拋出錯(cuò)誤。在 JQuery 的 Promise 對(duì)象的回調(diào)中拋出錯(cuò)誤是個(gè)糟糕的主意,因?yàn)殄e(cuò)誤不會(huì)被捕獲。
小結(jié)
最后一個(gè)例子揭示了,實(shí)現(xiàn) Promise 的關(guān)鍵是實(shí)現(xiàn)好 doResolve 方法,在完事以后觸發(fā)回調(diào)。而為了保證異步 setTimeout(fun, 0); 是關(guān)鍵一步。
Promise 一直用得蠻順手的,其很好的優(yōu)化了 NodeJS 異步處理時(shí)的代碼結(jié)構(gòu)。但是對(duì)于其工作原理卻有些懵懂和好奇,于是花了些精力查閱并翻譯了Promise 的規(guī)范,以充分的理解 Promise 的細(xì)節(jié)。
以上就是關(guān)于JavaScript中Promise的使用方法介紹,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
JS實(shí)現(xiàn)帶動(dòng)畫的回到頂部效果
這篇文章主要為大家詳細(xì) 介紹了JS實(shí)現(xiàn)帶動(dòng)畫的回到頂部效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
js實(shí)現(xiàn)鼠標(biāo)劃過(guò)給div加透明度的方法
這篇文章主要介紹了js實(shí)現(xiàn)鼠標(biāo)劃過(guò)給div加透明度的方法,涉及javascript動(dòng)態(tài)操作頁(yè)面元素屬性的相關(guān)技巧,該方法可兼容火狐與IE瀏覽器,需要的朋友可以參考下2015-05-05
JS實(shí)現(xiàn)鼠標(biāo)單擊與雙擊事件共存
本篇文章主要是對(duì)JS實(shí)現(xiàn)鼠標(biāo)單擊與雙擊事件共存的簡(jiǎn)單實(shí)例進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-03-03
Vue3基于countUp.js實(shí)現(xiàn)數(shù)字滾動(dòng)的插件
本文主要介紹了Vue3基于countUp.js實(shí)現(xiàn)數(shù)字滾動(dòng)的插件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
JavaScript動(dòng)態(tài)生成帶刪除行功能的表格
這篇文章主要為大家詳細(xì)介紹了JavaScript動(dòng)態(tài)生成帶刪除行功能的表格,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
JavaScript使用Max函數(shù)返回兩個(gè)數(shù)字中較大數(shù)的方法
這篇文章主要介紹了JavaScript使用Max函數(shù)返回兩個(gè)數(shù)字中較大數(shù)的方法,涉及javascript中Max函數(shù)的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
用javascript實(shí)現(xiàn)記錄來(lái)賓姓名的代碼
用javascript實(shí)現(xiàn)記錄來(lái)賓姓名的代碼...2007-03-03

