es6解讀5:promise

  • Promise作為ES6中最重要的特性之一浙炼,我們有必要掌握并理解透徹泳秀。本文將由淺到深丐枉,講解Promise的基本概念與使用方法。
  • Promise 是一個構(gòu)造函數(shù)髓霞,接收一個函數(shù)為參數(shù),這個函數(shù)有兩個形參 resolve 和 reject畦戒,分別表示異步操作執(zhí)行成功后的回調(diào)函數(shù)和異步操作執(zhí)行失敗后的回調(diào)函數(shù)方库。其實(shí)這里用“成功”和“失敗”來描述并不準(zhǔn)確,按照標(biāo)準(zhǔn)來講障斋,resolve是將Promise的狀態(tài)置為fullfiled纵潦,reject是將Promise的狀態(tài)置為rejected。

  • Promise對象有以下兩個特點(diǎn)垃环。

    (1)對象的狀態(tài)不受外界影響邀层。Promise對象代表一個異步操作,有三種狀態(tài):pending(進(jìn)行中)遂庄、fulfilled(已成功)和rejected(已失斄仍骸)。只有異步操作的結(jié)果涛目,可以決定當(dāng)前是哪一種狀態(tài)秸谢,任何其他操作都無法改變這個狀態(tài)凛澎。這也是Promise這個名字的由來,它的英語意思就是“承諾”估蹄,表示其他手段無法改變塑煎。
    
    (2)一旦狀態(tài)改變,就不會再變臭蚁,任何時候都可以得到這個結(jié)果最铁。Promise對象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected垮兑。只要這兩種情況發(fā)生冷尉,狀態(tài)就凝固了,不會再變了甥角,會一直保持這個結(jié)果网严,這時就稱為 resolved(已定型)。如果改變已經(jīng)發(fā)生了嗤无,你再對Promise對象添加回調(diào)函數(shù)震束,也會立即得到這個結(jié)果。這與事件(Event)完全不同当犯,事件的特點(diǎn)是垢村,如果你錯過了它,再去監(jiān)聽嚎卫,是得不到結(jié)果的嘉栓。
    
     new Promise(function(resolve, reject){
      //做一些異步操作
      setTimeout(function(){
        console.log('執(zhí)行完成');
        resolve('隨便什么數(shù)據(jù)');
      }, 2000);
    });
    
      在上面的代碼中,我們執(zhí)行了一個異步操作拓诸,也就是setTimeout侵佃,2秒后,輸出“執(zhí)行完成”奠支,并且調(diào)用resolve方法馋辈。
      運(yùn)行代碼,會在2秒后輸出“執(zhí)行完成”倍谜。注意迈螟!我只是new了一個對象,并沒有調(diào)用它尔崔,我們傳進(jìn)去的函數(shù)就已經(jīng)執(zhí)行了答毫,這是需要注意的一個細(xì)節(jié)。所以我們用Promise的時候一般是包在一個函數(shù)中季春,在需要的時候去運(yùn)行這個函數(shù)洗搂,如:
    
       function runAsync(){
              var p = new Promise(function(resolve, reject){
              //做一些異步操作
              setTimeout(function(){
                  console.log('執(zhí)行完成');
                  resolve('隨便什么數(shù)據(jù)');
              }, 2000);
          });
          return p;            
        }
    
        runAsync()
    
    • 這時候你應(yīng)該有兩個疑問:1.包裝這么一個函數(shù)有毛線用?2.resolve('隨便什么數(shù)據(jù)');這是干毛的?
    • 我們繼續(xù)來講蚕脏。在我們包裝好的函數(shù)最后侦副,會return出Promise對象,也就是說驼鞭,執(zhí)行這個函數(shù)我們得到了一個Promise對象秦驯。還記得Promise對象上有then、catch方法吧挣棕?這就是強(qiáng)大之處了译隘,看下面的代碼:
    runAsync().then(function(data){
                     console.log(data);
                     //后面可以用傳過來的數(shù)據(jù)做些其他操作
                     ......
                });
    
  • 在runAsync()的返回上直接調(diào)用then方法,then接收一個參數(shù)洛心,是函數(shù)固耘,并且會拿到我們在runAsync中調(diào)用resolve時傳的的參數(shù)。運(yùn)行這段代碼词身,會在2秒后輸出“執(zhí)行完成”厅目,緊接著輸出“隨便什么數(shù)據(jù)”。

  • 這時候你應(yīng)該有所領(lǐng)悟了法严,原來then里面的函數(shù)就跟我們平時的回調(diào)函數(shù)一個意思损敷,能夠在runAsync這個異步任務(wù)執(zhí)行完成之后被執(zhí)行。這就是Promise的作用了深啤,簡單來講拗馒,就是能把原來的回調(diào)寫法分離出來,在異步操作執(zhí)行完后溯街,用鏈?zhǔn)秸{(diào)用的方式執(zhí)行回調(diào)函數(shù)诱桂。

  • 你可能會不屑一顧,那么牛逼轟轟的Promise就這點(diǎn)能耐呈昔?我把回調(diào)函數(shù)封裝一下挥等,給runAsync傳進(jìn)去不也一樣嗎,就像這樣:

function runAsync(callback){
    setTimeout(function(){
        console.log('執(zhí)行完成');
        callback('隨便什么數(shù)據(jù)');
    }, 2000);
}

runAsync(function(data){
    console.log(data); //隨便什么數(shù)據(jù)
});

效果也是一樣的堤尾,還費(fèi)勁用Promise干嘛触菜。那么問題來了,有多層回調(diào)該怎么辦哀峻?如果callback也是一個異步操作,而且執(zhí)行完后也需要有相應(yīng)的回調(diào)函數(shù)哲泊,該怎么辦呢剩蟀?總不能再定義一個callback2,然后給callback傳進(jìn)去吧切威。而Promise的優(yōu)勢在于育特,可以在then方法中繼續(xù)寫Promise對象并返回,然后繼續(xù)調(diào)用then來進(jìn)行回調(diào)操作。

鏈?zhǔn)讲僮鞯挠梅?/h4>

*所以缰冤,從表面上看犬缨,Promise只是能夠簡化層層回調(diào)的寫法,而實(shí)質(zhì)上棉浸,Promise的精髓是“狀態(tài)”怀薛,用維護(hù)狀態(tài)、傳遞狀態(tài)的方式來使得回調(diào)函數(shù)能夠及時調(diào)用迷郑,它比傳遞callback函數(shù)要簡單枝恋、靈活的多。所以使用Promise的正確場景是這樣的:

runAsync1().then(function(data){
                console.log(data);
                return runAsync2();
                })
         .then(function(data){
                console.log(data);
                return runAsync3();
                })
        .then(function(data){
                console.log(data);
                });

這樣能夠按順序嗡害,每隔兩秒輸出每個異步回調(diào)中的內(nèi)容焚碌,在runAsync2中傳給resolve的數(shù)據(jù),能在接下來的then方法中拿到霸妹。運(yùn)行結(jié)果如下:


  • 在then方法中十电,你也可以直接return數(shù)據(jù)而不是Promise對象,在后面的then中就可以接收到數(shù)據(jù)了叹螟,比如我們把上面的代碼修改成這樣:
runAsync1().then(function(data){
                  console.log(data);
                   return runAsync2();
                })
                .then(function(data){
                    console.log(data);
                    return '直接返回數(shù)據(jù)';  //這里直接返回數(shù)據(jù)
                })
                  .then(function(data){
                     console.log(data);
                });

那么輸出就變成了這樣:

reject用法

到這里鹃骂,你應(yīng)該對“Promise是什么玩意”有了最基本的了解。那么我們接著來看看ES6的Promise還有哪些功能首妖。我們光用了resolve偎漫,還沒用reject呢,它是做什么的呢有缆?事實(shí)上象踊,我們前面的例子都是只有“執(zhí)行成功”的回調(diào),還沒有“失敗”的情況棚壁,reject的作用就是把Promise的狀態(tài)置為rejected杯矩,這樣我們在then中就能捕捉到,然后執(zhí)行“失敗”情況的回調(diào)袖外∈仿。看下面的代碼。

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的隨機(jī)數(shù)
            if(num<=5){
                resolve(num);
            }
            else{
                reject('數(shù)字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);
  • getNumber函數(shù)用來異步獲取一個數(shù)字曼验,2秒后執(zhí)行完成泌射,如果數(shù)字小于等于5,我們認(rèn)為是“成功”了鬓照,調(diào)用resolve修改Promise的狀態(tài)熔酷。否則我們認(rèn)為是“失敗”了,調(diào)用reject并傳遞一個參數(shù)豺裆,作為失敗的原因拒秘。
  • 運(yùn)行g(shù)etNumber并且在then中傳了兩個參數(shù),then方法可以接受兩個參數(shù),第一個對應(yīng)resolve的回調(diào)躺酒,第二個對應(yīng)reject的回調(diào)押蚤。所以我們能夠分別拿到他們傳過來的數(shù)據(jù)。多次運(yùn)行這段代碼羹应,你會隨機(jī)得到下面兩種結(jié)果:

或者

catch的用法

我們知道Promise對象除了then方法揽碘,還有一個catch方法,它是做什么用的呢量愧?其實(shí)它和then的第二個參數(shù)一樣钾菊,用來指定reject的回調(diào),用法是這樣:

getNumber()
.then(function(data){
  console.log('resolved');
  console.log(data);
})
.catch(function(reason){
  console.log('rejected');
  console.log(reason);
});

效果和寫在then的第二個參數(shù)里面一樣偎肃。不過它還有另外一個作用:在執(zhí)行resolve的回調(diào)(也就是上面then中的第一個參數(shù))時煞烫,如果拋出異常了(代碼出錯了),那么并不會報錯卡死js累颂,而是會進(jìn)到這個catch方法中滞详。請看下面的代碼:

getNumber()
.then(function(data){
  console.log('resolved');
  console.log(data);
  console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
  console.log('rejected');
  console.log(reason);
});

在resolve的回調(diào)中,我們console.log(somedata);而somedata這個變量是沒有被定義的紊馏。如果我們不用Promise料饥,代碼運(yùn)行到這里就直接在控制臺報錯了,不往下運(yùn)行了朱监。但是在這里岸啡,會得到這樣的結(jié)果:

也就是說進(jìn)到catch方法里面去了,而且把錯誤原因傳到了reason參數(shù)中赫编。即便是有錯誤的代碼也不會報錯了巡蘸,這與我們的try/catch語句有相同的功能。

all的用法

Promise的all方法提供了并行執(zhí)行異步操作的能力擂送,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)悦荒。我們?nèi)耘f使用上面定義好的runAsync1、runAsync2嘹吨、runAsync3這三個函數(shù)搬味,看下面的例子:

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
  console.log(results);
});

用Promise.all來執(zhí)行,all接收一個數(shù)組參數(shù)蟀拷,里面的值最終都算返回Promise對象碰纬。這樣,三個異步操作的并行執(zhí)行的问芬,等到它們都執(zhí)行完后才會進(jìn)到then里面嘀趟。那么,三個異步操作返回的數(shù)據(jù)哪里去了呢愈诚?都在then里面呢,all會把所有異步操作的結(jié)果放進(jìn)一個數(shù)組中傳給then,就是上面的results炕柔。所以上面代碼的輸出結(jié)果就是:

有了all酌泰,你就可以并行執(zhí)行多個異步操作,并且在一個回調(diào)中處理所有的返回數(shù)據(jù)匕累,是不是很酷陵刹?有一個場景是很適合用這個的,一些游戲類的素材比較多的應(yīng)用欢嘿,打開網(wǎng)頁時衰琐,預(yù)先加載需要用到的各種資源如圖片、flash以及各種靜態(tài)文件炼蹦。所有的都加載完后羡宙,我們再進(jìn)行頁面的初始化。

race的用法

all方法的效果實(shí)際上是「誰跑的慢掐隐,以誰為準(zhǔn)執(zhí)行回調(diào)」狗热,那么相對的就有另一個方法「誰跑的快,以誰為準(zhǔn)執(zhí)行回調(diào)」虑省,這就是race方法匿刮,這個詞本來就是賽跑的意思。race的用法與all一樣探颈,我們把上面runAsync1的延時改為1秒來看一下:

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
  console.log(results);
});

這三個異步操作同樣是并行執(zhí)行的熟丸。結(jié)果你應(yīng)該可以猜到,1秒后runAsync1已經(jīng)執(zhí)行完了伪节,此時then里面的就執(zhí)行了光羞。結(jié)果是這樣的:

Paste_Image.png

  • 你猜對了嗎?不完全架馋,是吧狞山。在then里面的回調(diào)開始執(zhí)行時,runAsync2()和runAsync3()并沒有停止叉寂,仍舊再執(zhí)行萍启。于是再過1秒后,輸出了他們結(jié)束的標(biāo)志屏鳍。
  • 這個race有什么用呢勘纯?使用場景還是很多的,比如我們可以用race給某個異步請求設(shè)置超時時間钓瞭,并且在超時后執(zhí)行相應(yīng)的操作驳遵,代碼如下:
//請求某個圖片資源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延時函數(shù),用于給請求計時
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('圖片請求超時');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函數(shù)會異步請求一張圖片山涡,我把地址寫為"xxxxxx"堤结,所以肯定是無法成功請求到的唆迁。timeout函數(shù)是一個延時5秒的異步操作。我們把這兩個返回Promise對象的函數(shù)放進(jìn)race竞穷,于是他倆就會賽跑唐责,如果5秒之內(nèi)圖片請求成功了,那么遍進(jìn)入then方法瘾带,執(zhí)行正常的流程鼠哥。如果5秒鐘圖片還未成功返回,那么timeout就跑贏了看政,則進(jìn)入catch朴恳,報出“圖片請求超時”的信息。運(yùn)行結(jié)果如下:


下面是一個用Promise對象實(shí)現(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 xhr= new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onreadystatechange = handler;
    xhr.responseType = "json";
    xhr.setRequestHeader("Accept", "application/json");
    xhr.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯了', error);
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末于颖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子厉萝,更是在濱河造成了極大的恐慌恍飘,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谴垫,死亡現(xiàn)場離奇詭異章母,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翩剪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門乳怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人前弯,你說我怎么就攤上這事蚪缀。” “怎么了恕出?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵询枚,是天一觀的道長。 經(jīng)常有香客問我浙巫,道長金蜀,這世上最難降的妖魔是什么痹仙? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任倔丈,我火速辦了婚禮,結(jié)果婚禮上巢块,老公的妹妹穿的比我還像新娘丧裁。我一直安慰自己护桦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布煎娇。 她就那樣靜靜地躺著二庵,像睡著了一般贪染。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上催享,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天抑进,我揣著相機(jī)與錄音,去河邊找鬼睡陪。 笑死,一個胖子當(dāng)著我的面吹牛匿情,可吹牛的內(nèi)容都是我干的兰迫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼炬称,長吁一口氣:“原來是場噩夢啊……” “哼汁果!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玲躯,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤据德,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跷车,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棘利,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年朽缴,在試婚紗的時候發(fā)現(xiàn)自己被綠了善玫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡密强,死狀恐怖茅郎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情或渤,我是刑警寧澤系冗,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站薪鹦,受9級特大地震影響掌敬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜距芬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一涝开、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧框仔,春花似錦舀武、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘪匿。三九已至,卻和暖如春寻馏,著一層夾襖步出監(jiān)牢的瞬間棋弥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工诚欠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顽染,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓轰绵,卻偏偏與公主長得像粉寞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子左腔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

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