JavaScript:Promise基本概念學(xué)習(xí)

  • 最早接觸Promise概念是在創(chuàng)業(yè)公司嫩絮,同事引入的Promise第三方庫,用在用Swift寫的工程中围肥。
  • 異步編程剿干,用block已經(jīng)是主流,分為成功穆刻、失敗置尔、最終三個(gè)分支。如果嵌套氢伟,那么將一層一層地嵌套進(jìn)去榜轿。在真正執(zhí)行的時(shí)候,“從內(nèi)向外”一層一層執(zhí)行朵锣∶危“從外到內(nèi)”寫,但是“從內(nèi)到外”執(zhí)行诚些,并且還是一層層嵌套飞傀,這個(gè)不符合人的思維習(xí)慣。Promise就是將這種情況統(tǒng)一調(diào)整為“從左到右”诬烹,書寫和執(zhí)行都一樣砸烦。并且把一層層的嵌套修改為一連串的點(diǎn)點(diǎn)點(diǎn)函數(shù)調(diào)用,這個(gè)“很人性化”绞吁。
  • 在函數(shù)式編程中幢痘,currycompose操作都是“從右向左”的,而Monad就是將操作改為“很人性化”的“從左向右”家破,書寫和執(zhí)行都是颜说。并且還可以把嵌套flatMap一下购岗,“鋪平”,從而形成一連串點(diǎn)點(diǎn)點(diǎn)函數(shù)調(diào)用门粪。
  • PromiseMonad針對的領(lǐng)域不同藕畔,不過都達(dá)到了相同的目標(biāo)(點(diǎn)點(diǎn)點(diǎn)函數(shù)鏈?zhǔn)秸{(diào)用,從左向右書寫和執(zhí)行)庄拇∽⒎可以把Promise看成是一種專門用于異步領(lǐng)域的Monad

什么是Promise

  • Promise是抽象異步處理對象以及對其進(jìn)行各種操作的組件。
  • 回調(diào)函數(shù)是處理異步過程的常用方式措近,傳給回調(diào)函數(shù)的參數(shù)為(error對象溶弟, 執(zhí)行結(jié)果)組合。
  • Node.js等規(guī)定在JavaScript的回調(diào)函數(shù)的第一個(gè)參數(shù)為Error對象瞭郑,這也是它的一個(gè)慣例辜御。
  • Promise則是把類似的異步處理對象和處理規(guī)則進(jìn)行規(guī)范化, 并按照采用統(tǒng)一的接口來編寫屈张,而采取規(guī)定方法之外的寫法都會(huì)出錯(cuò)擒权。

基本概念

  • 要想創(chuàng)建一個(gè)promise對象、可以使用new來調(diào)用Promise的構(gòu)造器來進(jìn)行實(shí)例化阁谆。
var promise = new Promise(function(resolve, reject) {
    // 異步處理
    // 處理結(jié)束后碳抄、調(diào)用resolve 或 reject
});
  • 對通過new生成的promise對象為了設(shè)置其值在 resolve(成功) /reject(失敗)時(shí)調(diào)用的回調(diào)函數(shù) 可以使用promise.then()實(shí)例方法。

  • onFulfilled、onRejected 兩個(gè)都為可選參數(shù)。一般情況围详,onFulfilled會(huì)用,onRejected省略璧尸,一般用catch比較多

promise.then(onFulfilled, onRejected);
  • promise.then成功和失敗時(shí)都可以使用。異常的時(shí)候調(diào)用 promise.catch(onRejected)

  • Promise 這樣的全局對象還擁有一些靜態(tài)方法熬拒。包括 Promise.all()還有 Promise.resolve()等在內(nèi)爷光,主要都是一些對Promise進(jìn)行操作的輔助方法。

  • new Promise實(shí)例化的promise對象有以下三個(gè)狀態(tài)澎粟。其中 左側(cè)為在 ES6 Promises規(guī)范中定義的術(shù)語蛀序, 而右側(cè)則是在 Promises/A+中描述狀態(tài)的術(shù)語“埔椋基本上狀態(tài)在代碼中是不會(huì)涉及到的哼拔,所以名稱也無需太在意引有。
    (1) "has-resolution" - Fulfilled: resolve(成功)時(shí)瓣颅。此時(shí)會(huì)調(diào)用 onFulfilled
    (2) "has-rejection" - Rejected: reject(失敗)時(shí)。此時(shí)會(huì)調(diào)用 onRejected
    (3) "unresolved" - Pending:既不是resolve也不是reject的狀態(tài)譬正。也就是promise對象剛被創(chuàng)建后的初始化狀態(tài)等

promise-states.png
  • promise對象的狀態(tài)宫补,從Pending轉(zhuǎn)換為FulfilledRejected之后檬姥, 這個(gè)promise對象的狀態(tài)就不會(huì)再發(fā)生任何變化。也就是說粉怕,PromiseEvent等不同健民,在.then后執(zhí)行的函數(shù)可以肯定地說只會(huì)被調(diào)用一次。

基本流程

  1. new Promise(fn)返回一個(gè)promise對象
  2. fn中指定異步等處理
    處理結(jié)果正常的話贫贝,調(diào)用resolve(處理結(jié)果值);
    處理結(jié)果錯(cuò)誤的話秉犹,調(diào)用reject(Error對象);
function getURL(URL) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = function () {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}
// 運(yùn)行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
    console.log(value);
}).catch(function onRejected(error){
    console.error(error);
});
  • 本質(zhì)上還是回調(diào)函數(shù)。將回調(diào)函數(shù)用promise對象包裝一下稚晚。
  • resolve方法的參數(shù)并沒有特別的規(guī)則崇堵,基本上把要傳給回調(diào)函數(shù)參數(shù)放進(jìn)去就可以了。 (then 方法可以接收到這個(gè)參數(shù)值)
  • 傳給reject 的參數(shù)也沒有什么特殊的限制客燕,一般只要是Error對象(或者繼承自Error對象)就可以鸳劳。
  • 其實(shí) .catch只是promise.then(undefined, onRejected) 的別名而已。一般說來也搓,使用.catch來將resolvereject處理分開來寫是比較推薦的做法赏廓。
  • Promise.resolve作為 new Promise()
    的快捷方式,在進(jìn)行promise對象的初始化或者編寫測試代碼的時(shí)候都非常方便傍妒。

異步執(zhí)行順序

在使用Promise.resolve(value)等方法的時(shí)候幔摸,如果promise對象立刻就能進(jìn)入resolve狀態(tài)的話,那么你是不是覺得.then里面指定的方法就是同步調(diào)用的呢颤练?
實(shí)際上抚太,.then中指定的方法調(diào)用是異步進(jìn)行的。下面是一個(gè)例子:

var promise = new Promise(function (resolve){
    console.log("inner promise"); // 1
    resolve(42);
});
promise.then(function(value){
    console.log(value); // 3
});
console.log("outer promise"); // 2
  • 由于JavaScript代碼會(huì)按照文件的從上到下的順序執(zhí)行昔案,所以最開始 <1> 會(huì)執(zhí)行尿贫,然后是resolve(42);被執(zhí)行。這時(shí)候 promise對象的已經(jīng)變?yōu)榇_定狀態(tài)踏揣,FulFilled被設(shè)置為了42 庆亡。
  • 下面的代碼 promise.then注冊了<3>這個(gè)回調(diào)函數(shù),但是這個(gè)是異步的捞稿,這里不會(huì)馬上執(zhí)行又谋。
  • 函數(shù)<2>會(huì)最先被調(diào)用,最后才會(huì)調(diào)用回調(diào)函數(shù)<3> 娱局。

輸出結(jié)果:

inner promise // 1
outer promise // 2
42            // 3

明明可以以同步方式進(jìn)行調(diào)用的函數(shù)彰亥,非要使用異步的調(diào)用方式,這是在Promise設(shè)計(jì)上的規(guī)定方針衰齐。

不要對異步回調(diào)函數(shù)進(jìn)行同步調(diào)用 Promise是符合這一點(diǎn)的任斋。

方法鏈的形式

Promise里可以將任意個(gè)方法連在一起作為一個(gè)方法鏈(method chain)

aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
    console.log(error);
});

如果把在then 中注冊的每個(gè)回調(diào)函數(shù)稱為task的話耻涛,那么我們就可以通過Promise方法鏈方式來編寫能以taskA → task B這種流程進(jìn)行處理的邏輯了废酷。

  • 每次調(diào)用then都會(huì)返回一個(gè)新創(chuàng)建的promise對象瘟檩。如果不用鏈?zhǔn)秸{(diào)用,那么就沒有順序執(zhí)行的效果澈蟆,要極力避免墨辛。
// 1: 對同一個(gè)promise對象同時(shí)調(diào)用 `then` 方法
var aPromise = new Promise(function (resolve) {
    resolve(100);
});
aPromise.then(function (value) {
    return value * 2;
});
aPromise.then(function (value) {
    return value * 2;
});
aPromise.then(function (value) {
    console.log("1: " + value); // => 100
})
// 這種是過程式調(diào)用,沒有順序執(zhí)行的功能趴俘,每個(gè)函數(shù)的參數(shù)value都是100睹簇,需要極力避免這種調(diào)用
// 2: 對 `then` 進(jìn)行 promise chain 方式進(jìn)行調(diào)用
var bPromise = new Promise(function (resolve) {
    resolve(100);
});
bPromise.then(function (value) {
    return value * 2;
}).then(function (value) {
    return value * 2;
}).then(function (value) {
    console.log("2: " + value); // => 100 * 2 * 2 = 400
});
// 這種鏈?zhǔn)秸{(diào)用,才是期望的樣子寥闪。
// 就算是簡單的值(這里是100)带膀,在傳遞的時(shí)候,傳的也是promise對象橙垢,中間有“裝箱”垛叨,“拆箱”的操作。
// then函數(shù)柜某,就是那種輸入輸出都是“類型”的函數(shù)嗽元,能夠形成鏈?zhǔn)秸{(diào)用。所以說promise是monad的一種實(shí)現(xiàn)
  • 在實(shí)際使用中喂击,要避免下面的錯(cuò)誤用法
function badAsyncCall() {
    var promise = Promise.resolve();
    promise.then(function() {
        // 任意處理
        return newVar;
    });
    return promise;
}

promise.then 中產(chǎn)生的異常不會(huì)被外部捕獲剂癌,也不能得到then 的返回值,即使其有返回值翰绊。原因是promise !== promise.then()

  • 應(yīng)該該為下面的正確形式
function anAsyncCall() {
    var promise = Promise.resolve();
    return promise.then(function() {
        // 任意處理
        return newVar;
    });
}

多個(gè)異步調(diào)用進(jìn)行統(tǒng)一處理

  • 為了應(yīng)對這種需要對多個(gè)異步調(diào)用進(jìn)行統(tǒng)一處理的場景佩谷,Promise準(zhǔn)備了 Promise.allPromise.race 這兩個(gè)靜態(tài)方法。
  • Promise.all 接收一個(gè)promise對象的數(shù)組作為參數(shù)监嗜,當(dāng)這個(gè)數(shù)組里的所有promise對象全部變?yōu)?code>resolve或reject狀態(tài)的時(shí)候谐檀,它才會(huì)去調(diào)用.then方法。
  • 傳遞給 Promise.allpromise并不是一個(gè)個(gè)的順序執(zhí)行的裁奇,而是同時(shí)開始桐猬、并行執(zhí)行的。
// `delay`毫秒后執(zhí)行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();
// 所有promise變?yōu)閞esolve后程序退出
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    // 約128ms
    console.log(values);    // [1,32,64,128]
});
// 輸出:有調(diào)度損耗刽肠,線程間同步損耗溃肪,理論上應(yīng)該輸出128ms,實(shí)際輸出145ms是合理的音五。多執(zhí)行幾次惫撰,這個(gè)值每次都會(huì)變
// 145ms
// [ 1, 32, 64, 128 ]
  • 4個(gè)promise是并行執(zhí)行的
  • 4個(gè)promise都執(zhí)行完畢后再執(zhí)行then
  • 傳遞的values數(shù)組和promise數(shù)組的順序一致
  • Promise.race 只要有一個(gè)promise對象進(jìn)入FulFilled 或者 Rejected 狀態(tài)的話,就會(huì)繼續(xù)進(jìn)行后面的處理躺涝。
// `delay`毫秒后執(zhí)行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
// 任何一個(gè)promise變?yōu)閞esolve或reject 的話程序就停止運(yùn)行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (value) {
    console.log(value);    // => 1
});
  • Promise.race 在第一個(gè)promise對象變?yōu)?code>Fulfilled之后厨钻,并不會(huì)取消其他promise對象的執(zhí)行。
var winnerPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log('winner funtion running');
            resolve('this is winner');
        }, 10);
    });
var loserPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log('loser funtion running');
            resolve('this is loser');
        }, 1000);
    });
// 第一個(gè)promise變?yōu)閞esolve后程序停止
Promise.race([winnerPromise, loserPromise]).then(function (value) {
    console.log(value);    // => 'this is winner'
});

// 輸出:
// winner funtion running
// this is winner
// loser funtion running
  • 數(shù)組中[winnerPromise, loserPromise]中的promise同時(shí)開始執(zhí)行
  • 10mswinnerPromise勝出,輸出winner funtion running
  • Promise.race()函數(shù)有了結(jié)果莉撇,就是winnerPromise呢蛤,順序執(zhí)行then惶傻,輸出傳遞的value棍郎,這里是this is winner。這個(gè)就是winnerPromiseresolve信息resolve('this is winner');
  • loserPromise對象沒有被取消银室,繼續(xù)執(zhí)行涂佃。1000ms后,loserPromise對象中的函數(shù)執(zhí)行蜈敢,輸出loser funtion running
    *loserPromise對象中的resolve信息'resolve('this is loser');不會(huì)被傳遞辜荠。

錯(cuò)誤處理

  • .then.catch都會(huì)創(chuàng)建并返回一個(gè) 新的promise對象。 Promise實(shí)際上每次在方法鏈中增加一次處理的時(shí)候所操作的都不是完全相同的promise對象抓狭。
then_catch.png
  • 使用promise.then(onFulfilled, onRejected) 的話伯病,在onFulfilled中發(fā)生異常的話,在 onRejected中是捕獲不到這個(gè)異常的否过。原因是他們在promise chain中處于同一級

  • promise.then(onFulfilled).catch(onRejected) 的情況下.then中產(chǎn)生的異常能在.catch中捕獲午笛。因?yàn)?code>.catch處于.then中的下一級

  • .then.catch在本質(zhì)上是沒有區(qū)別的,所不同的是在整個(gè)promise chain中所處的級別不同苗桂。.catch更靠后一點(diǎn)药磺,更可靠一點(diǎn)。

  • 如果一定要用.then來進(jìn)行錯(cuò)誤處理煤伟,那么就要新增一個(gè)下一級的.then來處理

function throwError(value) {
    // 拋出異常
    throw new Error(value);
}
// <1> onRejected不會(huì)被調(diào)用
function badMain(onRejected) {
    return Promise.resolve(42).then(throwError, onRejected);
}
// <2> 有異常發(fā)生時(shí)onRejected會(huì)被調(diào)用
function goodMain(onRejected) {
    return Promise.resolve(42).then(throwError).catch(onRejected);
}
// <3> 使用下一級的then
function nextThenMain(onRejected) {
    return Promise.resolve(42).then(throwError).then(null, onRejected);
}

// 運(yùn)行示例
badMain(function(){
    console.log("BAD");
});
goodMain(function(){
    console.log("GOOD");
});
nextThenMain(function(){
    console.log("Next Then");
});
// 輸出: 
// GOOD
// Next Then

這里的badMain癌佩,之所以捕獲不到錯(cuò)誤,是因?yàn)橛昧送患壍?code>.then
錯(cuò)誤處理統(tǒng)一使用.catch便锨,不要用.then

  • 主動(dòng)拋異常围辙,使用reject而不是throw
// 不要用throw,這雖然能運(yùn)行放案,但很不promise風(fēng)格酌畜,要避免
var promise = new Promise(function(resolve, reject){
    throw new Error("message");
});
promise.catch(function(error){
    console.error(error);// => "message"
});
// 這是推薦的方式,形成習(xí)慣
var promise = new Promise(function(resolve, reject){
    reject(new Error("message"));
});
promise.catch(function(error){
    console.error(error);// => "message"
})
  • 在.then中使用reject卿叽,推薦用下面的格式
var onRejected = console.error.bind(console);
var promise = Promise.resolve();
promise.then(function () {
    return Promise.reject(new Error("this promise is rejected"));
}).catch(onRejected);
//  this promise is rejected

參考文章

JavaScript Promise迷你書(中文版)

ES6 JavaScript Promise的感性認(rèn)知

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桥胞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子考婴,更是在濱河造成了極大的恐慌贩虾,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沥阱,死亡現(xiàn)場離奇詭異缎罢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門策精,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舰始,“玉大人,你說我怎么就攤上這事咽袜⊥杈恚” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵询刹,是天一觀的道長谜嫉。 經(jīng)常有香客問我,道長凹联,這世上最難降的妖魔是什么沐兰? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮蔽挠,結(jié)果婚禮上住闯,老公的妹妹穿的比我還像新娘。我一直安慰自己澳淑,他們只是感情好比原,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偶惠,像睡著了一般春寿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忽孽,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天绑改,我揣著相機(jī)與錄音,去河邊找鬼兄一。 笑死厘线,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的出革。 我是一名探鬼主播造壮,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼骂束!你這毒婦竟也來了耳璧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤展箱,失蹤者是張志新(化名)和其女友劉穎旨枯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體混驰,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攀隔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年皂贩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昆汹。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡明刷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出满粗,到底是詐尸還是另有隱情辈末,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布败潦,位于F島的核電站本冲,受9級特大地震影響准脂,放射性物質(zhì)發(fā)生泄漏劫扒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一狸膏、第九天 我趴在偏房一處隱蔽的房頂上張望沟饥。 院中可真熱鬧,春花似錦湾戳、人聲如沸贤旷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幼驶。三九已至,卻和暖如春韧衣,著一層夾襖步出監(jiān)牢的瞬間盅藻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工畅铭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氏淑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓硕噩,卻偏偏與公主長得像假残,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子炉擅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

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

  • Promiese 簡單說就是一個(gè)容器辉懒,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果,語法上說谍失,Pr...
    雨飛飛雨閱讀 3,361評論 0 19
  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗(yàn)的人眶俩,如果你還沒有使用過Promise,這篇文章可能不適合你袱贮,...
    HZ充電大喵閱讀 7,313評論 6 19
  • Promise的含義: ??Promise是異步編程的一種解決方案仿便,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,172評論 0 16
  • 00嗽仪、前言Promise 是異步編程的一種解決方案荒勇,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)...
    夜幕小草閱讀 2,134評論 0 12
  • JavaScript里通常不建議阻塞主程序闻坚,尤其是一些代價(jià)比較昂貴的操作沽翔,如查找數(shù)據(jù)庫,下載文件等操作窿凤,應(yīng)該用異步...
    張歆琳閱讀 2,759評論 0 12