異步回調(diào) 2 - ES6 Promise

如果瀏覽已經(jīng)有了Promise對象,那么頁就說明瀏覽器的js引擎里已經(jīng)有了Promsise隊列,這樣就可以利用Promise將任務(wù)放在它的隊列中

目前在瀏覽器中大部分已經(jīng)支持了Promise,除了IE,Promise是在ES6中javascript實現(xiàn)了prmose規(guī)范,和JQuery中的$.Deferred大概思路是一樣的,可能是部分API的用法有差異

Promise對象有三種狀態(tài),分別是

  • pending:等待/進行中,表示還沒有得到結(jié)果
  • resolved:已經(jīng)完成,表示得到了我們想要的結(jié)果,允許繼續(xù)向下執(zhí)行
  • rejected:表示結(jié)果錯誤,拒絕執(zhí)行

這三種狀態(tài)不受外界影響,而且狀態(tài)只能從pending改變?yōu)閞esolved或者rejected,并且不可逆,在Promise對象的構(gòu)造函數(shù)中,將一個函數(shù)作為第一個參數(shù),而這個函數(shù)就是用來處理Promise的狀態(tài)變化

new Prmise(function(resolve,reject){
  if(true){ resolve(msg)};
  if(false){ reject(msg)};
})

上面的resolve和reject都是一個函數(shù),他們的作用分別是將狀態(tài)修改為reslolved和rejected,resolve用于在異步操作成功時調(diào)用,并將異步操作的結(jié)果作為參數(shù)傳遞出去,reject會在異步操作失敗時調(diào)用,并將異步操作報出的錯誤作為參數(shù)傳遞出去

promise實例生成以后,可以使用then方法分別指定resolved狀態(tài)和rejectd狀態(tài)的回調(diào)函數(shù),該方法接收兩個回調(diào)函數(shù)作為參數(shù),第一個回調(diào)函數(shù)是promise對象的狀態(tài)變?yōu)閞esolved時調(diào)用,第二個回調(diào)函數(shù)是在promise對象狀態(tài)變?yōu)閞ejected時調(diào)用,其中第二個函數(shù)是可選的,這兩個函數(shù)都接收promise對象傳出的值作為參數(shù),注意一點promise中的方法會立即執(zhí)行,不會進入隊列,但是then中的方法會延后執(zhí)行,then中的方法會進入微隊列,在本次隊列的其它函數(shù)執(zhí)行完成之后再執(zhí)行

let promise=new Promise(function (resolve,reject) {
    console.log('promsie');
    resolve()
});

promise.then(function () {
    console.log('resolved');
})
    
console.log("HI");
//promise
//HI
//resolved

注意,調(diào)用resolve或reject并不會終結(jié)promise的參數(shù)的執(zhí)行,因為我們在在函數(shù)中調(diào)用resolve,resolve是在本輪事件循環(huán)末尾,是晚于本輪同步循環(huán)任務(wù)的,而且,一般來說,調(diào)用resolve或reject后,promise的使命就已經(jīng)完成了,不應該再在他后面寫操作,而應該吧操作放置到then中,所以一般會在調(diào)用resolve或reject時在它們前面加上return語句,防止意外

new Promise((resolve,reject)=>{
    return resolve("data");
    console.log("hello");//如果不添加return,那么hello仍然會輸出
})
    .then(data=>{
        console.log(data);
    })

reject函數(shù)的參數(shù)通常是Error對象的實例,表示拋出錯誤;resolve函數(shù)的參數(shù)除了正常的值以外,還可能是另一個promise實例

let p1=new Promise((resolve,reject)=>{
    setTimeout(()=>reject(new Error('fail')),2000)
})
let p2=new Promise((resolve,reject)=>{
    resolve(p1)
})

p2.then(success=>console.log(success))
  .catch(error=>console.error(error))  

//Error: fail

在接收p1之后,因為p1本身是一promise對象,所以在p2的狀態(tài)會被p1替換掉,p2自身的狀態(tài)無效了,由p1返回的狀態(tài)決定p2的狀態(tài),所以,p2的.then方法的觸發(fā)條件都會由p1來決定

下面用promise對象實現(xiàn)一個getJSON函數(shù)

var getJSON = function (url) {
    var promise = new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "qpplication/json");
        client.send();

        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.state === 200) {
                resolve(this.response)
            } else {
                reject(new Error(this.statusText));
            }
        };
    });
    return promise;
}

getJSON("/posts.json").then(function (data) {
    console.log(data);
}, function (error) {
    console.log(error);
})
Promise.prototype.then()

then方法是定義在原型對象Promise.prototype上的,需要注意的是then方法返回的是一個新的promise實例,所以可以采用鏈式寫法,在第一個回調(diào)函數(shù)完成以后,會將返回結(jié)果作為參數(shù),傳入第二個回調(diào)函數(shù)

new Promise((resolve,reject)=>{
  resolve(data)
})
  .then(data=>{
    console.log(data)
    return success;
  })
  .then(data=>{
    console.log(data)
  })
  // data
  // success

采用鏈式的then,可以指定一組按照次序調(diào)用的回調(diào)函數(shù),如果前一個函數(shù)返回的是一個promise對象,后一個回調(diào)函數(shù)就會等待該Promise對象的狀態(tài)發(fā)生變化才會被調(diào)用

Promise.prototype.catch()

該方法是.then(null,rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù),另外then方法指定的回調(diào)函數(shù),如果運行中拋出錯誤,也會被catch方法捕獲,Promise對象的錯誤具有"冒泡"性質(zhì),會一直向后傳遞,直到該錯誤被捕獲

new Promise((resolve, reject) => {
    resolve('success')
    reject("promise對象運行錯誤") //在已經(jīng)調(diào)用了resolve的情況下之后的reject不會起作用,該錯誤也不會被捕獲
                               //因為我們之前已經(jīng)了解promise對象的狀態(tài)一旦改變就不會再次發(fā)生變化
    })
    .catch(error=>console.error(error))
    .then(data => {
        console.log(data);
        throw new Error("then運行錯誤")
    })
    .catch(error=>console.error(error))
    //success
    //then運行錯誤

一般來說,我們不會在then中定義reject狀態(tài)的回調(diào)函數(shù),總是使用catch方法,因為使用catch方法不僅會捕獲當前promise對象的錯誤,也會捕獲之前錯誤

我們可以觀察下面的代碼

.catch(error=>console.error(error))
.then(null,error=>console.log(error))
//以上代碼是等價的

 try {
    x+2
}catch(e){
    reject(e)
}
reject(new Error("x is not defined"))
//以上代碼是等價的
//可以理解為reject的作用就是拋出錯誤

和傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù),promise對象拋出的錯誤不會傳遞到外層代碼,也不會有任何反應,Promise內(nèi)部的錯誤不會影響到Promise外部的代碼,通俗的說就是Promise會吃掉錯誤,這是我們需要注意的,所以就要求我們?nèi)绻覀働romise中代碼會對后續(xù)代碼有影響,一定注意要在Promise對象后面跟catch方法,這樣可以處理Promise內(nèi)部發(fā)生的錯誤,并且catch方法返回的還是一個Promise對象,因此后面還可以接著調(diào)用then方法,如果沒有報錯會跳過catch方法

new Promise((resolve,reject)=>resolve(x+2))
    .catch(error=>{
        if(error) {
            throw new Error(error)
          }
    })
    .then(data=>console.log(data));

console.log("下一個操作");
//如果沒有catch方法來拋出錯誤
//瀏覽器在運行到x+2時會報出 x is not defined 的錯誤
//但不會停止程序,下面的操作不受影響
Promise.all()

Promise.all()方法用于將多個Promise實例,包裝成一個新的Promise實例,類似于JQuery中$.when()方法,但不同之處在于,Promise.all()會自動將其中不是Promise實例的對象轉(zhuǎn)化為Promise實例對象然后再執(zhí)行函數(shù)

  • 如果其中有一個promise對象的狀態(tài)為pending,那么all的會等待其狀態(tài)變更后再執(zhí)行
  • 如果其中有一個promise對象的狀態(tài)為rejected,那么all的狀態(tài)就會變成rejected,并將第一個變?yōu)閞ejected狀態(tài)的實例的返回值會傳遞給all的回調(diào)函數(shù)
  • 只有所有的promise對象的狀態(tài)都是resolved,all的狀態(tài)才會變成resolved,此時所有傳入的promise對象的返回值組成一個數(shù)組返回給all的回調(diào)函數(shù)
const p1 = new Promise((resolve, result) => {
    console.log("p1");
    resolve();
})

const p2 = new Promise((resolve, result) => {
    console.log("p2");
    resolve();
})
    .then(() => {
        console.log("p2執(zhí)行完畢");
        return 2;
    })
const p3 = function () {
    console.log("p3執(zhí)行完畢");
}
Promise.all([p1, p2, p3])
    .then((success) => {
        console.log(success);
    })

如果在Promise對象自身有then或catch方法,那么會優(yōu)先執(zhí)行每一個promise對象的then和catch方法,因為then和catch方法會返回一個promise對象,所以在執(zhí)行完自身then和catch方法后會繼續(xù)執(zhí)行Promise.all的then和catch方法,如果當前的promise對象不存在then/catch方法,那么會調(diào)用Promise.all的then和catch方法,這里需要注意一點的是參數(shù)問題,如果調(diào)用了自身的then或catch方法,那么在Promise.all方法中接收的參數(shù)是then/catch中返回的值

Promise.race()

該方法和Promise.all()一樣,同樣是將多個promise實例包裝成一個新的Promise實例

區(qū)別在于在Promise.race()中只要其中一個參數(shù)的狀態(tài)發(fā)生改變,race的狀態(tài)就會發(fā)生改變,并將第一改變狀態(tài)的promise實例的返回值作為參數(shù)傳遞給race的回調(diào)函數(shù)

利用這個特性我們可以做一個如果函數(shù)執(zhí)行時間超過我們設(shè)定的時間后報錯的函數(shù)

 Promise.race([
    $.get('test.html',(res)=>{
        console.log(res)
    }),
    new Promise((resolve,reject)=>{
        setTimeout(()=>reject(new Error('request timeout')),5000)
    })
])
    .catch(error=>console.log(error))
    .then(success=>console.log(success))
Promise.resolve()

有時需要將現(xiàn)有對象轉(zhuǎn)化為Promise對象,就可以使用Promise.resolve()方法,返回值為新的Promise對象,我們在使用Promise.all()方法和Promise.race()方法時,這兩個方法對于我們傳入的不是promise實例的對象也是默認使用了Promise.resolve方法對其中的對象進行了轉(zhuǎn)換

例如我們要將一個JQuery的deferred對象轉(zhuǎn)化為一個新的Promise對象

var promise=Promise.resolve($.ajax('./test.txt'))
//上面的方法其實等價于下面的寫法
var promise=new Promise(resolve=>resolve($.ajax('./text.txt')))

Promise.resolve方法會根據(jù)傳入的參數(shù)不同分為四種情況

  1. 參數(shù)是一個promise實例

    那么Promise.resolve將不會做任何修改,直接返回該實例

  2. 參數(shù)是一個具有then方法的thenable對象

    thenable對象就是指那些具有then方法名的對象,Promise.resolve()在接收該方法后會將該對象轉(zhuǎn)化為promise對象,然后立即執(zhí)行該對象的then方法

    var thenable={
     then(){
         console.log("then方法被執(zhí)行啦");
     }
    }
    Promise.resolve(thenable) //then方法被執(zhí)行啦
    
  3. 如果參數(shù)不是promise對象,并且不具有then方法,那么回返回一個新的promise對象,狀態(tài)為resolved,并將自身返回給回調(diào)函數(shù)

    let  p={
            name:'tom'
        }
    p = Promise.resolve(p);
    p.then(function (res) {
     console.log(res);
    })
    //{name:'tom'}
    
  4. 不接收任何參數(shù)

    let p=Promise.resolve();
    
    p.then(function(){
      //...
    })
    
Promise.reject()

同Promise.resolve()的用法相同,不同的是會返回一個狀態(tài)為rejected的實例,回調(diào)函數(shù)會立即執(zhí)行

需要注意一點,Promise.reject()方法的參數(shù),會不做任何改變的作為回調(diào)函數(shù)的參數(shù)傳入,這也是兩個方法的區(qū)別所在

let thenable={
    then(resolve,reject){
        reject("error")
    }
}
Promise.reject(thenable)
    .catch(err=>console.log(err === thenable))
//true

上面的代碼最后的回調(diào)函數(shù)是將thenable方法整體作為參數(shù)接收了,而不是我們傳入的error

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380