趁著過年,講講 Promise
趁著過年,將講 Promise
想象一下,你是一位頂級歌手,粉絲們?nèi)杖找挂苟荚跒槟慵磳l(fā)行的歌曲而發(fā)愁。
為了緩解壓力,你答應(yīng)出版后寄給他們。你給你的粉絲一個列表。他們可以填寫自己的電子郵件地址,這樣當(dāng)歌曲可用時,所有訂閱方都能立即收到。即使出了什么大問題,比如工作室著火了,你不能發(fā)布這首歌,他們還是會得到通知。
每個人都快樂:你,因為人們不再擠你了,還有粉絲,因為他們不會錯過這首歌。
這是我們在編程中經(jīng)常遇到的現(xiàn)實類比:
一個“生成代碼”,做一些事情,并需要時間。例如,通過網(wǎng)絡(luò)加載數(shù)據(jù)的一些代碼。這是一個“歌手”。
一旦“生產(chǎn)代碼”準(zhǔn)備好了,“消費(fèi)代碼”就會想得到它的結(jié)果。許多函數(shù)可能需要這個結(jié)果。這些就是“粉絲”。
promise是一個特殊的JavaScript對象,它將“生產(chǎn)代碼”和“消費(fèi)代碼”鏈接在一起。根據(jù)我們的類比:這是“訂閱列表”。“生成代碼”需要花費(fèi)任何時間來生成承諾的結(jié)果,而“承諾”在結(jié)果準(zhǔn)備好時使所有訂閱的代碼都可以使用該結(jié)果。
這種類比并不十分準(zhǔn)確,因為JavaScript承諾比簡單的訂閱列表更復(fù)雜:它們有額外的特性和限制。但從一開始就很好。
promise對象的構(gòu)造函數(shù)語法是:
let?promise?=?new?Promise(function(resolve,?reject)?{
??//?executor?(the?producing?code,?"singer")
});
傳遞給new Promise的函數(shù)稱為executor。創(chuàng)建新承諾時,執(zhí)行程序自動運(yùn)行。它包含最終產(chǎn)生結(jié)果的生成代碼。用上面的比喻:執(zhí)行人就是“歌手”。
它的參數(shù)resolve和reject是JavaScript本身提供的回調(diào)函數(shù)。我們的代碼只在執(zhí)行器內(nèi)部。
當(dāng)executor獲得結(jié)果時,不管是快還是晚,都沒有關(guān)系,它應(yīng)該調(diào)用以下其中一個回調(diào)函數(shù):
resolve(value)—如果作業(yè)成功完成,則使用結(jié)果值。
reject(error)——如果發(fā)生了錯誤,error就是error對象。
總而言之:執(zhí)行程序自動運(yùn)行并嘗試執(zhí)行一項工作。當(dāng)它完成嘗試時,如果成功就調(diào)用resolve,如果有錯誤就調(diào)用reject。
新的promise構(gòu)造函數(shù)返回的promise對象有以下內(nèi)部屬性:

狀態(tài)——最初是“pending”,然后在調(diào)用resolve時更改為“completed”,在調(diào)用reject時更改為“rejected”。
result——最初未定義,然后在調(diào)用resolve(value)時更改為value,在調(diào)用reject(error)時更改為error。
因此執(zhí)行人最終將promise移動到以下狀態(tài)之一:
稍后我們將看到“粉絲”如何訂閱這些變化。
下面是一個promise構(gòu)造函數(shù)和一個簡單的executor函數(shù),它的“生成代碼”需要花費(fèi)時間(通過setTimeout):
let?promise?=?new?Promise(function(resolve,?reject)?{
??//?the?function?is?executed?automatically?when?the?promise?is?constructed
??//?after?1?second?signal?that?the?job?is?done?with?the?result?"done"
??setTimeout(()?=>?resolve("done"),?1000);
});
運(yùn)行上面的代碼,我們可以看到兩件事:
執(zhí)行程序被自動且立即地(通過new Promise)調(diào)用。
執(zhí)行器接收兩個參數(shù):resolve和reject。這些函數(shù)是由JavaScript引擎預(yù)先定義的,所以我們不需要創(chuàng)建它們。我們準(zhǔn)備好了就叫他們其中一個。
在一秒鐘的“處理”之后,執(zhí)行程序調(diào)用resolve(“完成”)來生成結(jié)果。這會改變promise對象的狀態(tài):

這是一個成功完成工作的例子,一個“fulfilled prommise”。
下面是一個 Promise 執(zhí)行人用錯誤 reject promise 的例子:
let?promise?=?new?Promise(function(resolve,?reject)?{
??//?after?1?second?signal?that?the?job?is?finished?with?an?error
??setTimeout(()?=>?reject(new?Error("Whoops!")),?1000);
});
reject(…)的調(diào)用將promise對象移動到“rejected”狀態(tài):

總而言之,執(zhí)行者應(yīng)該執(zhí)行一項工作(通常需要花費(fèi)時間),然后調(diào)用resolve或reject來更改相應(yīng)promise對象的狀態(tài)。
被解決或被拒絕的承諾稱為“已解決”,而不是最初的“待解決”承諾。
執(zhí)行程序應(yīng)該只調(diào)用一個resolve或一個拒絕。任何狀態(tài)的改變都是最終的。
所有進(jìn)一步的resolve和reject調(diào)用都被忽略:
let?promise?=?new?Promise(function(resolve,?reject)?{
??resolve("done");
??reject(new?Error("…"));?//?ignored
??setTimeout(()?=>?resolve("…"));?//?ignored
});
其思想是執(zhí)行者完成的工作可能只有一個結(jié)果或一個錯誤。
同樣,resolve/reject只期望一個參數(shù)(或none),并將忽略其他參數(shù)。
萬一出了問題,遺囑執(zhí)行人應(yīng)該調(diào)用reject。這可以用任何類型的參數(shù)來完成(就像resolve)。但是建議使用Error對象(或者從Error繼承的對象)。這樣做的理由很快就會變得顯而易見。
在實踐中,執(zhí)行程序通常異步執(zhí)行一些操作,并在一段時間后調(diào)用resolve/reject,但它并不需要這樣做。我們也可以立即調(diào)用resolve或reject,像這樣:
let?promise?=?new?Promise(function(resolve,?reject)?{
??//?not?taking?our?time?to?do?the?job
??resolve(123);?//?immediately?give?the?result:?123
});
例如,這可能發(fā)生在當(dāng)我們開始做一個工作,但然后看到所有事情都已經(jīng)完成和緩存。
這很好。我們立即有了一個解決的承諾。
