javascript中Promise

1.Promise是什么孝偎?

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大岔帽。它由社區(qū)最早提出和實現(xiàn)昵仅,ES6 將其寫進了語言標準,統(tǒng)一了用法梢卸,原生提供了Promise對象走诞。

2. 基本用法

Promise對象是一個構造函數(shù),用來生成Promise實例蛤高。下面我們來

1.創(chuàng)建一個promise實例:

const promise = new Promise(function(resolve, reject) {
    // ... some code
    if (/* 異步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});
  • Promise構造函數(shù)接受一個函數(shù)作為參數(shù)蚣旱,該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù)戴陡,由 JavaScript 引擎提供塞绿,不用自己部署。
  • resolve函數(shù)的作用是恤批,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?fulfilled)异吻,在異步操作成功時調(diào)用,并將異步操作的結果喜庞,作為參數(shù)傳遞出去诀浪;
  • reject函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)赋荆,在異步操作失敗時調(diào)用笋妥,并將異步操作報出的錯誤,作為參數(shù)傳遞出去窄潭。

2.使用promise實例

Promise實例生成以后春宣,可以用then方法分別指定fulfilled狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù),從而接收傳遞過來的狀態(tài)嫉你。

promise.then(function(value) {
    // success
}, function(error) {
    // failure
});
  • then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)月帝。
  • 第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閒ulfilled時調(diào)用
  • 第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞ejected時調(diào)用。其中幽污,第二個函數(shù)是可選的嚷辅,不一定要提供
  • 這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)。

3. 特點

從上面的用法看距误,我們可以了解到Promise有如下特點:

  1. Promise對象代表一個異步操作簸搞,有三種狀態(tài):pending(進行中)扁位、fulfilled(已成功)和rejected(已失敗)趁俊。只有調(diào)用了resolve/reject函數(shù)來處理結果域仇,才可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)寺擂。這也是Promise這個名字的由來暇务,它的英語意思就是“承諾”,表示其他手段無法改變怔软。

舉個例子:

var get = new Promise(function (resolve, reject) {
    console.log(1);
    resolve(2)
    console.log(3)
    setTimeout(function(){
        console.log(5)
    },1);
})
get.then(function (value) {
    console.log(value);
})
console.log(4);

//輸出結果為:1 3 4 2 5

從上面例子我們可以看出垦细,只要使用new Promise,相當于創(chuàng)建一個異步操作挡逼。只有調(diào)用resolve或者reject才能觸發(fā)異步操作括改。所以我們一般在promise內(nèi)放置異步操作(如請求等),當返回結果挚瘟,調(diào)用resolve或者reject叹谁。這里為了簡潔明了說明其是異步,所以沒有放異步操作乘盖,直接調(diào)用了resolve函數(shù)焰檩,從打印結果就可以看出其是異步操作。

  1. Promise對象的狀態(tài)改變订框,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected析苫。當處于pending狀態(tài)時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)穿扳。

我們更改上面例子:

var get = new Promise(function (resolve, reject) {
    console.log(1);
    resolve(2)
    reject(5)
    console.log(3)
})
get.then(function (value) {
    console.log(value);
},function(error){
    console.log(error);
})
console.log(4);

//輸出結果仍為:1 3 4 2

從例子結果可以看出衩侥,promise對象要么是fulfilled,要么是rejected矛物,所以不能同時輸出

  1. 一般來說茫死,調(diào)用resolve或reject以后,Promise 的使命就完成了履羞,后繼操作應該放到then方法里面峦萎,而不應該直接寫在resolve或reject的后面。所以忆首,最好在它們前面加上return語句爱榔,這樣就不會有意外。

    new Promise((resolve, reject) => {
    return resolve(1);
    // 后面的語句不會執(zhí)行
    console.log(2);
    })

下面是一個用Promise對象實現(xiàn)的 Ajax 操作的例子糙及。

const getJSON = function(url) {
    const promise = new Promise(function(resolve, reject){
        const handler = function() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
        const client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

    });
    return promise;
};

getJSON("/posts.json").then(function(json) {
    console.log('Contents: ' + json);
}, function(error) {
    console.error('出錯了', error);
});

4. Promise.prototype.then()

Promise 實例具有then方法详幽,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態(tài)改變時的回調(diào)函數(shù)唇聘。前面說過版姑,then方法的第一個參數(shù)是resolved狀態(tài)(已定型,指的是fulfilled狀態(tài)))的回調(diào)函數(shù)雳灾,第二個參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)漠酿。

上面我們講過 回調(diào)地域問題,現(xiàn)在我們用promise進行鏈式調(diào)用來解決該問題

const request = url => { 
    return new Promise((resolve, reject) => {
        $.get(url, data => {
            resolve(data)
        });
    })
};

request(url).then(data1 => {
    return request(data1.url);   
}).then(data2 => {
    return request(data2.url);
}).then(data3 => {
    console.log(data3);
})

從代碼上看谎亩,是不是更直觀更簡潔。

5. Promise.prototype.catch

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名宇姚,用于指定發(fā)生錯誤時的回調(diào)函數(shù)匈庭。我們分別講解then函數(shù)和catch函數(shù),對比兩者的區(qū)別:

5.1 then函數(shù)的第二個參數(shù):錯誤回調(diào)rejection

1.當調(diào)用reject函數(shù)時候浑劳,執(zhí)行rejection回調(diào)

var get = new Promise(function (resolve, reject) {
    reject(a)
})
get.then(null, function (error) {
    console.log(error); //1
})

運行結果如下:

20190925150628.png

2.當程序運行錯誤阱持,執(zhí)行rejection回調(diào)

var get = new Promise(function (resolve, reject) {
      console.log(a);
})
get.then(null, function (error) {
    console.log(error); 
})

運行結果和上面一樣:


20190925150628.png

由上面2個例子我們可以知道,reject等同于拋出錯誤魔熏。供后面回調(diào)函數(shù)捕獲錯誤衷咽。

2.Promise 內(nèi)部的錯誤不會影響到 Promise 外部的代碼運行,通俗的說法就是“Promise 會吃掉錯誤”(js不會因為錯誤崩潰導致下面代碼無法運行)蒜绽。

var get = new Promise(function (resolve, reject) {
    reject(a)
})
console.log(1);
setTimeout(function(){
    console.log(2)
},1);

運行結果如下:

20190925144458.png

從結果可以看出镶骗,我們并沒有使用回調(diào)函數(shù)來捕獲錯誤,所以瀏覽器會報錯躲雅,但是并沒有終止腳本運行鼎姊。

3.Promise 的狀態(tài)一旦改變,就永久保持該狀態(tài)相赁,不會再變了相寇。所以在resolve語句后面,再拋出錯誤钮科,不會被捕獲

var get = new Promise(function (resolve, reject) {
    resolve('ok')
    console.log(a)
    console.log(3)
})
get.then(null,function(error){
    console.log(error); //沒有執(zhí)行
})
console.log(1);
setTimeout(function(){
    console.log(2)
},1);

// 1 2

從上面例子可以看出唤衫,在resolve('ok')后面調(diào)用未定義變量a拋出錯誤,回調(diào)函數(shù)并沒有執(zhí)行捕獲绵脯。在這有個奇怪的問題佳励,上面我們在講解promise特點時候,舉過例子證明resolve后面語句也會執(zhí)行桨嫁,這里為什么打印結果不是 3 1 2?

這里需要注意的是:由于resolve后面拋出錯誤植兰,所以后面語句會被阻止運行(同步異步都會被阻止)。如果沒有拋出錯誤璃吧,則正常運行楣导。

5.2 catch函數(shù)

上面講了then函數(shù)的第二個參數(shù):我們知道了如果在promise中如果有錯,可以使用錯誤回調(diào)rejection進行捕獲畜挨。那catch呢筒繁?和rejection一樣噩凹,也一樣可以捕獲。

var get = new Promise(function (resolve, reject) {
    reject(a)
})
get.catch(function (error) {
    console.log(error); //1
})

運行結果如下:

20190925150628.png

那么他們兩個到底有什么區(qū)別呢毡咏?唯一的區(qū)別就是驮宴,如果在 then 的第一個成功回調(diào)函數(shù)里拋出了異常,catch 能捕獲到呕缭,而錯誤回調(diào)函數(shù)捕獲不到

var get = new Promise(function (resolve, reject) {
    resolve('ok')
})
get.then(function (value) {
    console.log(a);
    console.log(value); //因報錯被阻止運行
},function(error){
    console.log(error); //沒有執(zhí)行捕獲堵泽,所以瀏覽器報錯
})

所以運行結果如下:

20190925151125.png

現(xiàn)在我們改成catch就可以捕獲

var get = new Promise(function (resolve, reject) {
    resolve('ok')
})
get.then(function (value) {
    console.log(a);
    console.log(value); //因報錯被阻止運行
}).catch(function(error){
    console.log(error); //可以捕獲
})

運行結果如下:

20190925151248.png

所以一般總是建議,不用rejection回調(diào)函數(shù)來捕獲錯誤恢总,用catch方法迎罗,這樣既可以處理 Promise 內(nèi)部發(fā)生的錯誤,又可以處理成功回調(diào)函數(shù)中的錯誤片仿。

有的人可能會好奇纹安,那假若都有錯誤怎么辦?

var get = new Promise(function (resolve, reject) {
  console.log(e);
  resolve('ok')  //報錯所以直接進入捕獲函數(shù)砂豌,因為只可能有一種狀態(tài)
  console.log(1); //因報錯被阻止運行
})
get.then(function (value) {
  console.log(value); //不會調(diào)用
}).catch(function(error){
    console.log(error);
})

運行結果如下:


20190925151924.png

Promise對象的狀態(tài)改變厢岂,只可能有一種結果,要么成功阳距,要么失敗塔粒,不能改變,因為上面直接報錯娄涩,所以直接進入catch函數(shù)中窗怒。這里只以catch舉例,實際此時用catch或者rejection回調(diào)函數(shù)都可以蓄拣。

注意:如果同時用rejection回調(diào)函數(shù)和catch函數(shù)捕獲錯誤扬虚,只會優(yōu)先執(zhí)行rejection回調(diào)函數(shù),不會執(zhí)行catch函數(shù)球恤。

6. Promise.prototype.finally

finally方法用于指定不管 Promise 對象最后狀態(tài)如何辜昵,都會執(zhí)行的操作。該方法是 ES2018 引入標準的

var get = new Promise(function (resolve, reject) {
  resolve(1)  //報錯所以直接進入捕獲函數(shù)咽斧,因為只可能有一種狀態(tài)
})
get.then(function (value) {
  console.log(value); //不會調(diào)用
}).catch(function(error){
    console.log(error);
}).finally(function(){
    console.log(2);
})
console.log(3);
setTimeout(() => {
    console.log(4);
}, 0);

// 3 1 2 4

這里只舉了成功狀態(tài)堪置,失敗狀態(tài)一樣,就不舉例了张惹。

7. Promise.resolve(value)

該方法的作用是:把value轉換成Promise對象舀锨。這里說的Promise對象指的是由Promise構造函數(shù)生成的實例。

如果傳入不同類型的 value 值宛逗,返回結果也有區(qū)別:

  1. value本身就是Promise對象坎匿,返回結果和入?yún)alue相同
let get = new Promise(function (resolve, reject) {
    resolve(1) 
})
let p1= Promise.resolve(get)
console.log(p1===get); //true
  1. value是個thenable對象(即該對象有屬性名字為then方法),返回結果為Promise對象,其跟隨 thenable 對象中的then函數(shù)狀態(tài)(resolved/rejected)
//demo1
let thenable = {
    then: function (resolve, reject) {
        resolve(1);
    }
};
let p1 = Promise.resolve(thenable)
p1.then(function(value){
    console.log(value); //1
})

//demo2
let thenable = {
    then: function (resolve, reject) {
        reject(2);
    }
};
let p1 = Promise.resolve(thenable)
p1.then(null,function(error){
    console.log(error) //2
})

我們平常用的jquery的ajax就是thenable對象,我們打印看下:

 console.log($.ajax());

打印結果如下圖:

20190925175951.png
  1. value不是具有then方法的對象替蔬,或根本就不是對象如字符串告私、數(shù)值等,返回結果為一個resolved狀態(tài)的 Promise 對象
const p = Promise.resolve('Hello');
p.then(function (s){
    console.log(s)
});
console.log(p)

打印結果如下圖:

20190926110002.png

所以承桥,假如我們用錯誤回調(diào)函數(shù)接收驻粟,是不會執(zhí)行的。

const p = Promise.resolve('Hello');
p.then(null,function (error){
    console.log(error)
});
console.log(p);

打印結果如下圖:

20190926110359.png
  1. value不傳凶异,返回結果為一個resolved狀態(tài)的 Promise 對象
 const p = Promise.resolve();
 p.then(function (s){
     console.log(s)
 });
 console.log(p);

打印結果如下圖:

20190926111545.png

8. Promise.reject(value)

該方法的作用是:把value轉換成Promise對象蜀撑。這里說的Promise對象指的是由Promise構造函數(shù)生成的實例。

如果傳入不同類型的 value 值唠帝,返回結果都為一個rejected狀態(tài)的 Promise實例(和上面講的resolve方法不一樣)屯掖。所以調(diào)用該實例會直接調(diào)用catch方法或者then第二個參數(shù),其值就是傳進去的value值襟衰。

  1. value是promise對象
let get = new Promise(function (resolve, reject) {
    resolve(1) 
})
let p1= Promise.reject(get)
p1.catch(function(value){
    console.log(value===get); //true
})

注意,如果value為promise對象粪摘,該對象里面不能用reject或者語法錯誤瀑晒,具體原因暫不清楚。

let get = new Promise(function (resolve, reject) {
    reject(1) 
})
let p1= Promise.reject(get)
p1.catch(function(value){
    console.log(value===get);
})
20190926162816.png
  1. value是個thenable對象
let thenable = {
    then: function (resolve, reject) {
        resolve(1);
    }
};
let p1 = Promise.resolve(thenable)
p1.catch(function(value){
    console.log(value===thenable); //true
})
  1. value不是具有then方法的對象徘意,或根本就不是對象如字符串苔悦、數(shù)值等
const p = Promise.reject('Hello');
p.catch(function (s){
    console.log(s)
}); //Hello
  1. value不傳
let p1 = Promise.reject()
p1.catch(function(value){
    console.log(value);
})
console.log(1) 
//1 undefined

注意,如果不傳并且不catch捕獲椎咧,會報錯玖详。比如下面代碼:

 let p1 = Promise.reject()
 console.log(1) 

運行結果如下:

20190926163107.png

9. Promise.all(arr)

Promise.all方法用于將多個 Promise 實例,包裝成一個新的 Promise 實例勤讽。參數(shù)為數(shù)組蟋座。用法如下:

const p = Promise.all([p1, p2, p3]);

上面代碼中,Promise.all方法接受一個數(shù)組作為參數(shù)脚牍,p1向臀、p2、p3都是 Promise 實例诸狭,如果不是券膀,就會先調(diào)用之前講到的Promise.resolve方法,將參數(shù)轉為 Promise 實例驯遇,再進一步處理芹彬。

p的狀態(tài)由p1、p2叉庐、p3決定舒帮,分成兩種情況。

  • 只有p1、p2会前、p3的狀態(tài)都變成fulfilled好乐,p的狀態(tài)才會變成fulfilled。此時p1瓦宜、p2蔚万、p3的返回值組成一個數(shù)組,傳遞給p的then回調(diào)函數(shù)临庇。
  • 只要p1反璃、p2、p3之中有一個被rejected假夺,p的狀態(tài)就變成rejected淮蜈,此時第一個被reject的實例的返回值,會傳遞給p的錯誤回調(diào)函數(shù)已卷。

10. Promise.race(arr)

Promise.race方法同樣是將多個 Promise 實例梧田,包裝成一個新的 Promise 實例。參數(shù)為數(shù)組侧蘸。用法如下:

const p = Promise.race([p1, p2, p3]);

上面代碼中裁眯,Promise.all方法接受一個數(shù)組作為參數(shù),p1讳癌、p2穿稳、p3都是 Promise 實例,如果不是晌坤,就會先調(diào)用之前講到的Promise.resolve方法逢艘,將參數(shù)轉為 Promise 實例,再進一步處理骤菠。

和all方法不同的是:

  • 只要p1它改、p2、p3之中有一個實例率先改變狀態(tài)娩怎,p的狀態(tài)就跟著改變搔课。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調(diào)函數(shù)截亦。

參考來源:

《ES6標準入門——阮一峰》
面試精選之Promise

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爬泥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子崩瓤,更是在濱河造成了極大的恐慌袍啡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件却桶,死亡現(xiàn)場離奇詭異境输,居然都是意外死亡蔗牡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門嗅剖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辩越,“玉大人,你說我怎么就攤上這事信粮∏埽” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵强缘,是天一觀的道長铭拧。 經(jīng)常有香客問我窖壕,道長,這世上最難降的妖魔是什么状知? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任谜洽,我火速辦了婚禮地梨,結果婚禮上仲器,老公的妹妹穿的比我還像新娘稻爬。我一直安慰自己,他們只是感情好秘车,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布留拾。 她就那樣靜靜地躺著,像睡著了一般鲫尊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沦偎,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天疫向,我揣著相機與錄音,去河邊找鬼豪嚎。 笑死搔驼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的侈询。 我是一名探鬼主播舌涨,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扔字!你這毒婦竟也來了囊嘉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤革为,失蹤者是張志新(化名)和其女友劉穎扭粱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體震檩,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡琢蛤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年蜓堕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片博其。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡套才,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出慕淡,到底是詐尸還是另有隱情背伴,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布儡率,位于F島的核電站挂据,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儿普。R本人自食惡果不足惜崎逃,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眉孩。 院中可真熱鬧个绍,春花似錦、人聲如沸浪汪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽死遭。三九已至广恢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呀潭,已是汗流浹背钉迷。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钠署,地道東北人糠聪。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像谐鼎,于是被迫代替她去往敵國和親舰蟆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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

  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案狸棍,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,698評論 1 56
  • 原文地址:http://es6.ruanyifeng.com/#docs/promise Promise 的含義 ...
    AI云棧閱讀 863評論 0 7
  • 你不知道JS:異步 第三章:Promises 在第二章身害,我們指出了采用回調(diào)來表達異步和管理并發(fā)時的兩種主要不足:缺...
    purple_force閱讀 2,052評論 0 4
  • //本文內(nèi)容起初摘抄于 阮一峰 作者的譯文,用于記錄和學習隔缀,建議觀者移步于原文 概念: 所謂的Promise题造,...
    曾經(jīng)過往閱讀 1,225評論 0 7
  • 前言 本文旨在簡單講解一下javascript中的Promise對象的概念,特性與簡單的使用方法猾瘸。并在文末會附上一...
    _暮雨清秋_閱讀 2,183評論 0 3