在JavaScript中,回調(diào)函數(shù)是處理異步編程的常用解決方案,但層層嵌套的回調(diào)金字塔(如下代碼所示)一直受人詬病,因?yàn)椴粌H在視覺(jué)上更加混亂,而且在管理上也更為復(fù)雜。
setTimeout(() => { var reason = "成功執(zhí)行"; setTimeout(() => {
console.log(reason); },500); }, 500);
Promise是ES6新增的特性,能更合理的控制和追蹤異步操作。它是一個(gè)包含狀態(tài)、可繼承的對(duì)象,不僅能管理而不是依賴回調(diào),還能以同步的方式傳遞異步的計(jì)算結(jié)果,從而避免陷入回調(diào)金字塔的泥潭中。鏈?zhǔn)剑创?lián)起來(lái))的Promise讓代碼有更高的可讀性和更便捷的調(diào)試性。下面會(huì)用Promise實(shí)現(xiàn)上一個(gè)示例的功能。
var promise = new Promise(function(resolve, reject) { setTimeout(() => {
resolve("成功執(zhí)行"); }, 500); }); promise.then(function(value) { setTimeout(() => {
console.log(value); },500); });
示例只是為了能對(duì)Promise有個(gè)初步的認(rèn)識(shí),其中涉及的Promise的創(chuàng)建、then()方法等概念,都將在接下來(lái)的章節(jié)中做詳細(xì)的講解。
?
一、狀態(tài)
Promise依據(jù)其狀態(tài)的變化,讓異步操作變得有序,而Promise有三種互斥的狀態(tài)可供選擇,具體如下所列。
(1)pending:等待中,初始狀態(tài),此時(shí)還未處理(Promise中的)異步操作。
(2)fulfilled:已完成,異步操作成功時(shí)的狀態(tài)。
(3)rejected:已拒絕,異步操作失敗時(shí)的狀態(tài)。
每個(gè)Promise只能維護(hù)一個(gè)狀態(tài),并且狀態(tài)只會(huì)朝一個(gè)方向變化,即從pending變?yōu)閒ulfilled或rejected,而fulfilled不能變?yōu)閞ejected,反之也一樣,這種處理狀態(tài)的行為也叫決議。注意,Promise會(huì)在內(nèi)部處理狀態(tài)的變化,并且由于ES6對(duì)外沒(méi)有暴露訪問(wèn)Promise狀態(tài)的屬性或方法,因此無(wú)法在外部判斷Promise當(dāng)前處在哪個(gè)狀態(tài)。
二、創(chuàng)建
如果要使用Promise,那么需要先初始化,可以通過(guò)構(gòu)造函數(shù)的方式創(chuàng)建一個(gè)Promise實(shí)例,如下所示。
var promise = new Promise(function(resolve, reject) { /* executor */ });
構(gòu)造函數(shù)Promise()能接收一個(gè)執(zhí)行器(executor),即帶有resolve和reject兩個(gè)參數(shù)的函數(shù),執(zhí)行器會(huì)在構(gòu)造函數(shù)返回新實(shí)例前被調(diào)用。它的兩個(gè)參數(shù)也是函數(shù),分別適合不同的語(yǔ)境,具體如下所列。
(1)在執(zhí)行器中的異步操作完成時(shí)會(huì)調(diào)用resolve()函數(shù),當(dāng)前Promise的狀態(tài)會(huì)根據(jù)它的參數(shù)發(fā)生變化。當(dāng)參數(shù)為空或非Promise時(shí),當(dāng)前狀態(tài)變成fulfilled;當(dāng)參數(shù)是Promise時(shí),當(dāng)前Promise的狀態(tài)和參數(shù)的相同。
(2)在執(zhí)行器中的異步操作錯(cuò)誤時(shí)會(huì)調(diào)用reject()函數(shù),當(dāng)前Promise的狀態(tài)會(huì)變成rejected。
resolve()和reject()都能接收一個(gè)參數(shù)(即決議結(jié)果),前者的參數(shù)可以是本次操作的結(jié)果;而后者的參數(shù)可以是操作失敗的理由,它們都會(huì)傳遞給下一個(gè)異步操作。
三、then()
在生成Promise實(shí)例之后,就能通過(guò)then()方法綁定狀態(tài)變化后的回調(diào)函數(shù)(即處理方法),如下代碼所示,此處是異步操作同步化的關(guān)鍵。
promise.then(function(value) { // success }, function(reason) { // failure });
then()方法的兩個(gè)參數(shù),可分別指定狀態(tài)變成fulfilled和rejected后的回調(diào)函數(shù),而這兩個(gè)回調(diào)函數(shù)的參數(shù)分別來(lái)自于resolve()和reject()函數(shù)。通過(guò)then()方法的這兩個(gè)回調(diào)函數(shù)就能清晰的反饋出異步操作是否成功執(zhí)行了。
由于then()方法的返回值是一個(gè)新的Promise實(shí)例,因此可以鏈?zhǔn)秸{(diào)用then()方法,按順序綁定回調(diào)函數(shù),如下所示。
var chain = new Promise(function(resolve, reject) { reject("error"); });
chain.then(null, function(reason) { console.log(reason); //"error" return "end"
; }) .then(function(value) { console.log(value); //"end" });
雖然第一個(gè)then()方法中的已完成的回調(diào)函數(shù)是null,但并不會(huì)終止數(shù)據(jù)的傳遞,仍然是先輸出“error”,再輸出“end”。之所以是這樣的輸出順序,與回調(diào)函數(shù)的執(zhí)行順序有關(guān)。在then()方法鏈中,當(dāng)前Promise的狀態(tài)會(huì)決定下一個(gè)then()方法執(zhí)行哪個(gè)回調(diào)函數(shù),而這個(gè)狀態(tài)又會(huì)受回調(diào)函數(shù)和它的返回值的影響,具體如下所列。
(1)當(dāng)返回值是一個(gè)非Promise的值時(shí),其狀態(tài)會(huì)變成fulfilled。
(2)當(dāng)回調(diào)函數(shù)拋出一個(gè)錯(cuò)誤時(shí),其狀態(tài)會(huì)變成rejected。
(3)當(dāng)返回值是一個(gè)Promise時(shí),其狀態(tài)與返回值的相同。
下面有一個(gè)示例,描述了第三種情況,其中Promise.resolve()創(chuàng)建了已完成的Promise,相當(dāng)于新創(chuàng)建一個(gè)在執(zhí)行器中調(diào)用resolve()函數(shù)的Promise;Promise.reject()創(chuàng)建了已拒絕的Promise;catch()方法能處理拒絕的回調(diào)函數(shù)。這些都將在隨后的章節(jié)中做詳細(xì)介紹。
var chain = new Promise(function(resolve, reject) { resolve(); }); chain.then(
function(value) { return Promise.resolve("fulfilled"); //相當(dāng)于 return new Promise(
function(resolve) { resolve("fulfilled"); }); }) .then(function(value) {
console.log(value);//"fulfilled" return Promise.reject("rejected"); }) .catch(
function(reason) { console.log(reason); //"rejected" });
四、thenable
包含then()方法的對(duì)象被稱為thenable,所有的Promise都是thenable,下面是一個(gè)自定義的thenable,then()方法的參數(shù)含義與Promise中的相同。
let tha = { then(resolve, reject) { reject("thenable"); } };
Promise.resolve()能接收一個(gè)thenable,并返回一個(gè)新的Promise實(shí)例,將上一個(gè)示例的tha對(duì)象傳遞給它,如下所示。
Promise.resolve(tha) .catch(function(reason) { console.log(reason); //thenable
}).then(function() { console.log("end"); });
Promise.resolve()能將thenable轉(zhuǎn)換成已完成或已拒絕的Promise,其最終的狀態(tài)取決于thenable的then()方法,像這個(gè)示例中的then()方法調(diào)用了reject()函數(shù),因此新的Promise的狀態(tài)是已拒絕(rejected)。
五、錯(cuò)誤處理
Promise的catch()方法可以捕獲并處理前一個(gè)異步操作中拋出的錯(cuò)誤,它能接收一個(gè)已拒絕的回調(diào)函數(shù)(onRejected),其行為相當(dāng)于調(diào)用一個(gè)忽略已完成的回調(diào)函數(shù)的then()方法,例如像下面這樣第一個(gè)參數(shù)傳null或undefined。
catch(onRejected) //相當(dāng)于 then(null, onRejected) then(undefined, onRejected)
下面是一個(gè)使用了catch()方法的例子,先在執(zhí)行器中拋出一個(gè)錯(cuò)誤,然后在catch()方法中處理。
var error = new Promise(function(resolve, reject) { throw "error info"; });
error.catch(function(reason) { console.log(reason); //"error info" });
如果Promise的狀態(tài)在拋出錯(cuò)誤之前被改變,那么這個(gè)錯(cuò)誤就不能被catch()方法捕獲,如下所示。
var error = new Promise(function(resolve, reject) { resolve(); throw "error
info"; }); error.catch(function(reason) { console.log(reason); //不會(huì)輸出 });
在執(zhí)行器中,由于resolve()函數(shù)在throw語(yǔ)句之前被調(diào)用,因此“error info”這句錯(cuò)誤理由就不能在catch()方法中輸出。
在鏈?zhǔn)降腜romise中,一旦發(fā)生錯(cuò)誤,那么這個(gè)錯(cuò)誤在沒(méi)被捕獲前,會(huì)一直傳遞下去。為了確保所有的錯(cuò)誤都能被處理,可在鏈的末尾加上catch()方法。
?
熱門工具 換一換
