Promise

在學(xué)習(xí)本章節(jié)內(nèi)容前,你需要先了解什么是異步編程,可以參考:JavaScript 異步編程

Promise 是一個(gè) ECMAScript 6 提供的類(lèi),目的是更加優(yōu)雅地書(shū)寫(xiě)復(fù)雜的異步任務(wù)。

由于 Promise 是 ES6 新增加的,所以一些舊的瀏覽器并不支持,Promise 對(duì)象代表了未來(lái)將要發(fā)生的事件患蹂,用來(lái)傳遞異步操作的消息。

構(gòu)造 Promise

現(xiàn)在我們新建一個(gè) Promise 對(duì)象:

Promise 構(gòu)造函數(shù)包含一個(gè)參數(shù)和一個(gè)帶有 resolve(解析)和 reject(拒絕)兩個(gè)參數(shù)的回調(diào)砸紊。在回調(diào)中執(zhí)行一些操作(例如異步)传于,如果一切都正常,則調(diào)用 resolve醉顽,否則調(diào)用 reject沼溜。

new Promise(function (resolve, reject) {
    // 要做的事情...(異步處理)
    // 處理結(jié)束后、調(diào)用resolve 或 reject
});

通過(guò)新建一個(gè) Promise 對(duì)象好像并沒(méi)有看出它怎樣 "更加優(yōu)雅地書(shū)寫(xiě)復(fù)雜的異步任務(wù)"游添。我們之前遇到的異步任務(wù)都是一次異步系草,如果需要多次調(diào)用異步函數(shù)呢?例如唆涝,如果我想分三次輸出字符串找都,第一次間隔 1 秒,第二次間隔 4 秒廊酣,第三次間隔 3 秒:

setTimeout(function () {
    console.log("First");
    setTimeout(function () {
        console.log("Second");
        setTimeout(function () {
            console.log("Third");
        }, 3000);
    }, 4000);
}, 1000);

//First
//Second
//Third

這段程序?qū)崿F(xiàn)了這個(gè)功能能耻,但是它是用 "函數(shù)瀑布" 來(lái)實(shí)現(xiàn)的。可想而知晓猛,在一個(gè)復(fù)雜的程序當(dāng)中饿幅,用 "函數(shù)瀑布" 實(shí)現(xiàn)的程序無(wú)論是維護(hù)還是異常處理都是一件特別繁瑣的事情,而且會(huì)讓縮進(jìn)格式變得非常冗贅戒职。

現(xiàn)在我們用 Promise 來(lái)實(shí)現(xiàn)同樣的功能:

new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve();
    }, 1000);
}).then(function () {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("Second");
            resolve();
        }, 4000);
    });
}).then(function () {
    setTimeout(function () {
        console.log("Third");
    }, 3000);
});

//First
//Second
//Third

這段代碼較長(zhǎng)栗恩,所以還不需要完全理解它,我想引起注意的是 Promise 將嵌套格式的代碼變成了順序格式的代碼洪燥。

Promise 的使用

下面我們通過(guò)剖析這段 Promise "計(jì)時(shí)器" 代碼來(lái)講述 Promise 的使用:

Promise 構(gòu)造函數(shù)只有一個(gè)參數(shù)磕秤,是一個(gè)函數(shù),這個(gè)函數(shù)在構(gòu)造之后會(huì)直接被異步運(yùn)行蚓曼,所以我們稱(chēng)之為起始函數(shù)亲澡。起始函數(shù)包含兩個(gè)參數(shù) resolve 和 reject。

當(dāng) Promise 被構(gòu)造時(shí)纫版,起始函數(shù)會(huì)被異步執(zhí)行:

new Promise(function (resolve, reject) {
    console.log("Run");
});

這段程序會(huì)直接輸出 Run。

resolve 和 reject 都是函數(shù)客情,其中調(diào)用 resolve 代表一切正常其弊,reject 是出現(xiàn)異常時(shí)所調(diào)用的:

new Promise(function (resolve, reject) {
    var a = 0;
    var b = 1;
    if (b == 0) reject("Divide zero");
    else resolve(a / b);
}).then(function (value) {
    console.log("a / b = " + value);
}).catch(function (err) {
    console.log(err);
}).finally(function () {
    console.log("End");
});

這段程序執(zhí)行結(jié)果是:

a / b = 0
End

Promise 類(lèi)有.then() .catch().finally()三個(gè)方法,這三個(gè)方法的參數(shù)都是一個(gè)函數(shù)膀斋,.then() 可以將參數(shù)中的函數(shù)添加到當(dāng)前 Promise 的正常執(zhí)行序列梭伐,.catch() 則是設(shè)定 Promise 的異常處理序列,.finally() 是在 Promise 執(zhí)行的最后一定會(huì)執(zhí)行的序列仰担。 .then() 傳入的函數(shù)會(huì)按順序依次執(zhí)行糊识,有任何異常都會(huì)直接跳到 catch 序列:

new Promise(function (resolve, reject) {
    console.log(1111); 
    resolve(2222);  //輸出2222數(shù)據(jù)
}).then(function (value) {
    console.log(value);  //打印2222數(shù)據(jù)
    return 3333;  //輸出3333數(shù)據(jù)
}).then(function (value) {
    console.log(value); //打印3333數(shù)據(jù)
    throw "An error";  //拋出錯(cuò)誤數(shù)據(jù),終止后續(xù) then 操作
}).catch(function (err) {
    console.log(err);  //打印錯(cuò)誤數(shù)據(jù)
});

執(zhí)行結(jié)果:

1111
2222
3333
An error

resolve() 中可以放置一個(gè)參數(shù)用于向下一個(gè) then 傳遞一個(gè)值,then 中的函數(shù)也可以返回一個(gè)值傳遞給 then摔蓝。但是赂苗,如果 then 中返回的是一個(gè) Promise 對(duì)象,那么下一個(gè) then 將相當(dāng)于對(duì)這個(gè)返回的 Promise 進(jìn)行操作贮尉,這一點(diǎn)從剛才的計(jì)時(shí)器的例子中可以看出來(lái)拌滋。

reject() 參數(shù)中一般會(huì)傳遞一個(gè)異常給之后的 catch 函數(shù)用于處理異常。

比較下then 中返回的是對(duì)象 / 值

then 中返回的是一個(gè) Promise 對(duì)象

如果返回的是Promise 對(duì)象猜谚,第一個(gè)回調(diào)函數(shù)完成以后败砂,會(huì)將返回結(jié)果作為參數(shù),傳入第二個(gè)回調(diào)函數(shù)魏铅。
如果前一個(gè)回調(diào)函數(shù)返回的是Promise對(duì)象昌犹,這時(shí)后一個(gè)回調(diào)函數(shù)就會(huì)等待該P(yáng)romise對(duì)象有了運(yùn)行結(jié)果,才會(huì)進(jìn)一步調(diào)用览芳。(獲取結(jié)果順序不變(相當(dāng)于同步編程))

new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve();
    }, 1000);
}).then(function () {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("Second");
            resolve();
        }, 4000);
    });
}).then(function () {
    setTimeout(function () {
        console.log("Third");
    }, 3000);
});

//First
//Second
//Third
then 中返回的是一個(gè)值

如果返回的是值斜姥,獲取結(jié)果順序?qū)⒏鶕?jù) 時(shí)間先后 排列, 而且第二個(gè)then無(wú)法獲取Promise數(shù)據(jù),只能獲取上一個(gè)then中返回的數(shù)據(jù)疾渴,沒(méi)有則返回undefined

new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve("Second")
    }, 1000);
}).then(function (value) {  //第一個(gè)then
        setTimeout(function () {
            console.log(value);
        }, 3000);
        return "Third"
}).then(function (value) {  //第二個(gè)then
    setTimeout(function () {
        console.log(value);
    }, 2000);
});

//First
//Third
//Second

但是請(qǐng)注意以下兩點(diǎn):

  • resolve 和 reject 的作用域只有起始函數(shù)千贯,不包括 then 以及其他序列;
  • resolve 和 reject 并不能夠使起始函數(shù)停止運(yùn)行搞坝,別忘了 return搔谴。

Promise 函數(shù)

上述的 "計(jì)時(shí)器" 程序看上去比函數(shù)瀑布還要長(zhǎng),所以我們可以將它的核心部分寫(xiě)成一個(gè) Promise 函數(shù):

//有setTimeout
function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

//無(wú)setTimeout
function print( message) {
    return new Promise(function (resolve, reject) {
            console.log(message);
            resolve();
    });
}

然后我們就可以放心大膽的實(shí)現(xiàn)程序功能了:

//有setTimeout
print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

//無(wú)setTimeout
print("First").then(function () {
    return print("Second");
}).then(function () {
    print("Third");
});

這種返回值為一個(gè) Promise 對(duì)象的函數(shù)稱(chēng)作 Promise 函數(shù)桩撮,它常常用于開(kāi)發(fā)基于異步操作的庫(kù)敦第。

回答常見(jiàn)的問(wèn)題(FAQ)

Q: then、catch 和 finally 序列能否順序顛倒店量?

A: 可以芜果,效果完全一樣。但不建議這樣做融师,最好按 then-catch-finally 的順序編寫(xiě)程序右钾。

Q: 除了 then 塊以外,其它兩種塊能否多次使用旱爆?

A: 可以舀射,finally 與 then 一樣會(huì)按順序執(zhí)行,但是 catch 塊只會(huì)執(zhí)行第一個(gè)怀伦,除非 catch 塊里有異常脆烟。所以最好只安排一個(gè) catch 和 finally 塊。

Q: then 塊如何中斷房待?

A: then 塊默認(rèn)會(huì)向下順序執(zhí)行邢羔,return 是不能中斷的,可以通過(guò) throw 來(lái)跳轉(zhuǎn)至 catch 實(shí)現(xiàn)中斷桑孩。

Q: 什么時(shí)候適合用 Promise 而不是傳統(tǒng)回調(diào)函數(shù)拜鹤?

A: 當(dāng)需要多次順序執(zhí)行異步操作的時(shí)候,例如洼怔,如果想通過(guò)異步方法先后檢測(cè)用戶(hù)名和密碼署惯,需要先異步檢測(cè)用戶(hù)名,然后再異步檢測(cè)密碼的情況下就很適合 Promise镣隶。

Q: Promise 是一種將異步轉(zhuǎn)換為同步的方法嗎极谊?

A: 完全不是。Promise 只不過(guò)是一種更良好的編程風(fēng)格安岂。

Q: 什么時(shí)候我們需要再寫(xiě)一個(gè) then 而不是在當(dāng)前的 then 接著編程轻猖?

A: 當(dāng)你又需要調(diào)用一個(gè)異步任務(wù)的時(shí)候。

異步函數(shù)

異步函數(shù)(async function)是 ECMAScript 2017 (ECMA-262) 標(biāo)準(zhǔn)的規(guī)范域那,幾乎被所有瀏覽器所支持咙边,除了 Internet Explorer猜煮。

在 Promise 中我們編寫(xiě)過(guò)一個(gè) Promise 函數(shù):

function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

然后用不同的時(shí)間間隔輸出了三行文本:

print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

我們可以將這段代碼變得更好看:async / await

async function asyncFunc() {
    await print(1000, "First");
    await print(4000, "Second");
    await print(3000, "Third");
}
asyncFunc();

//First
//Second
//Third

哈!這豈不是將異步操作變得像同步操作一樣容易了嗎败许!

這次的回答是肯定的王带,異步函數(shù) async function 中可以使用 await 指令,await 指令后必須跟著一個(gè) Promise市殷,異步函數(shù)會(huì)在這個(gè) Promise 運(yùn)行中暫停愕撰,直到其運(yùn)行結(jié)束再繼續(xù)運(yùn)行。

異步函數(shù)實(shí)際上原理與 Promise 原生 API 的機(jī)制是一模一樣的醋寝,只不過(guò)更便于程序員閱讀搞挣。

處理異常的機(jī)制將用 try-catch 塊實(shí)現(xiàn):

async function asyncFunc() {
    try{
        await print(1000, "First");
        await print(4000, "Second");
        await print(3000, "Third");
        throw "error"; 
    }catch(err){
        console.log(err)
    }

}
asyncFunc();

//First
//Second
//error

如果 Promise 有一個(gè)正常的返回值,await 語(yǔ)句也會(huì)返回它:

async function asyncFunc() {
    let value = await new Promise(
        function (resolve, reject) {
            resolve("Return value");
        }
    );
    console.log(value);
}
asyncFunc();

程序會(huì)輸出:

Return value

Promise 對(duì)象有以下兩個(gè)特點(diǎn):

1音羞、對(duì)象的狀態(tài)不受外界影響囱桨。Promise 對(duì)象代表一個(gè)異步操作,有三種狀態(tài):

pending: 初始狀態(tài)嗅绰,不是成功或失敗狀態(tài)舍肠。
fulfilled: 意味著操作成功完成。
rejected: 意味著操作失敗窘面。
只有異步操作的結(jié)果貌夕,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)民镜。這也是 Promise 這個(gè)名字的由來(lái),它的英語(yǔ)意思就是「承諾」险毁,表示其他手段無(wú)法改變制圈。

2、一旦狀態(tài)改變畔况,就不會(huì)再變鲸鹦,任何時(shí)候都可以得到這個(gè)結(jié)果。Promise 對(duì)象的狀態(tài)改變跷跪,只有兩種可能:從 Pending 變?yōu)?Resolved 和從 Pending 變?yōu)?Rejected馋嗜。只要這兩種情況發(fā)生,狀態(tài)就凝固了吵瞻,不會(huì)再變了葛菇,會(huì)一直保持這個(gè)結(jié)果。就算改變已經(jīng)發(fā)生了橡羞,你再對(duì) Promise 對(duì)象添加回調(diào)函數(shù)眯停,也會(huì)立即得到這個(gè)結(jié)果。這與事件(Event)完全不同卿泽,事件的特點(diǎn)是莺债,如果你錯(cuò)過(guò)了它,再去監(jiān)聽(tīng),是得不到結(jié)果的齐邦。

Promise 優(yōu)缺點(diǎn)

有了 Promise 對(duì)象椎侠,就可以將異步操作以同步操作的流程表達(dá)出來(lái),避免了層層嵌套的回調(diào)函數(shù)措拇。此外我纪,Promise 對(duì)象提供統(tǒng)一的接口,使得控制異步操作更加容易儡羔。

Promise 也有一些缺點(diǎn)宣羊。首先,無(wú)法取消 Promise汰蜘,一旦新建它就會(huì)立即執(zhí)行仇冯,無(wú)法中途取消。其次族操,如果不設(shè)置回調(diào)函數(shù)苛坚,Promise 內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部色难。第三泼舱,當(dāng)處于 Pending 狀態(tài)時(shí),無(wú)法得知目前進(jìn)展到哪一個(gè)階段(剛剛開(kāi)始還是即將完成)枷莉。

【筆記】

Promise 對(duì)象代表一個(gè)異步操作娇昙,有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成笤妙,又稱(chēng) Fulfilled)和 Rejected(已失斆罢啤)。

通過(guò)回調(diào)里的 resolve(data) 將這個(gè) promise 標(biāo)記為 resolverd蹲盘,然后進(jìn)行下一步 then((data)=>{//do something})股毫,resolve 里的參數(shù)就是你要傳入 then 的數(shù)據(jù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末召衔,一起剝皮案震驚了整個(gè)濱河市铃诬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苍凛,老刑警劉巖趣席,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異毫深,居然都是意外死亡吩坝,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)哑蔫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钉寝,“玉大人弧呐,你說(shuō)我怎么就攤上這事∏陡伲” “怎么了俘枫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逮走。 經(jīng)常有香客問(wèn)我鸠蚪,道長(zhǎng),這世上最難降的妖魔是什么师溅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任茅信,我火速辦了婚禮,結(jié)果婚禮上墓臭,老公的妹妹穿的比我還像新娘蘸鲸。我一直安慰自己,他們只是感情好窿锉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布酌摇。 她就那樣靜靜地躺著,像睡著了一般嗡载。 火紅的嫁衣襯著肌膚如雪窑多。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天洼滚,我揣著相機(jī)與錄音埂息,去河邊找鬼。 笑死遥巴,一個(gè)胖子當(dāng)著我的面吹牛耿芹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挪哄,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琉闪!你這毒婦竟也來(lái)了迹炼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤颠毙,失蹤者是張志新(化名)和其女友劉穎斯入,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛀蜜,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刻两,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滴某。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磅摹。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滋迈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出户誓,到底是詐尸還是另有隱情饼灿,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布帝美,位于F島的核電站碍彭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悼潭。R本人自食惡果不足惜庇忌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舰褪。 院中可真熱鬧皆疹,春花似錦、人聲如沸抵知。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刷喜。三九已至残制,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掖疮,已是汗流浹背初茶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浊闪,地道東北人恼布。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搁宾,于是被迫代替她去往敵國(guó)和親折汞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容