JavaScript - Promise 學(xué)習(xí)筆記一

Promise 是個(gè)啥?

馬上拿起來(lái)了一個(gè)有道詞典查了一下.

Promise翻譯

承諾,保證的意思.
很多電影里都有這句臺(tái)詞:I Promise You.


是什么問(wèn)題導(dǎo)致了ES6要給一個(gè)Promise?

在開(kāi)發(fā)web應(yīng)用的過(guò)程中,AJAX異步請(qǐng)求數(shù)據(jù)很常見(jiàn).

$.get('./data/users/1',function(data){
    //.....
})

一個(gè)頁(yè)面請(qǐng)求多個(gè)接口,每個(gè)接口之間有相互依賴關(guān)系也很常見(jiàn).

$.get('./data/users/1',function(data){
    $.get('./data/products/${data.userid}',function(data){
        $.get('./data/accountInfo/${data.productAccountInfo}',function(data){
            //......
        })
    })
})

有問(wèn)題嗎? callback 被丟到了 Event Loop 里了,執(zhí)行時(shí)機(jī)除了它自己,沒(méi)人能知道.所以,為了獲取上一個(gè)接口的數(shù),我們需要在它里面在發(fā)送下一個(gè) Ajax 請(qǐng)求.

強(qiáng)行問(wèn)題在于:如果這樣的接口依賴很多呢?

所謂的callback hell --- 回調(diào)地獄??

callback hell

對(duì),代碼從原來(lái)的線性自上而下,變成了現(xiàn)在的橫向發(fā)展.

但不管它們變成什么樣.

起碼在上述和我簡(jiǎn)單的demo中的代碼,有幾個(gè)共同點(diǎn)

  • 都是 callback 回調(diào)的方法.
  • 都是下一個(gè)接口請(qǐng)求依賴上一個(gè)接口返回的數(shù)據(jù).

Promise 如何解決代上述代碼橫向發(fā)展的問(wèn)題的?

為了解決代碼丑陋和難以維護(hù)(代碼寫(xiě)的太臃腫,太丑了,確實(shí)也就變得很難維護(hù)了)問(wèn)題.

于是 ES6 新推出了一個(gè)叫 Promise 的玩意.

感性認(rèn)知一下


userDataPromise('./data/users/1')
    .then((data)=>{
        // 在這里拿到了用戶數(shù)據(jù)
       return productsDataPrmoise('./data/products/${data.userid}')
    })
    .then((data)=>{
        // 在這里拿到的商品信息
        return accountDataPromise('./data/accountInfo/${data.productAccountInfo}')
    })
    .then((data)=>{
        // 在這里拿到了賬戶信息,然后該做什么就作什么.
        //.....
    })

這個(gè)和上述使用 $.get 的共同點(diǎn).

  • 都是一個(gè)請(qǐng)求發(fā)完之后,接著發(fā)下一個(gè)請(qǐng)求.下一個(gè)請(qǐng)求依賴上一個(gè)請(qǐng)求的數(shù)據(jù).
  • 每個(gè) Promise使用 then???來(lái)執(zhí)行回調(diào)函數(shù).
  • 代碼的結(jié)構(gòu)是很舒服的縱向發(fā)展.

這個(gè)和上述 $.get 的不同點(diǎn)

  • $.get 的回調(diào)函數(shù),我們是在一個(gè)方法(,function(){callbackhere})里寫(xiě)的.
  • 第二個(gè)$.get嵌套在了第一個(gè)$.get的callback里面.
  • 代碼是橫向發(fā)展的

僅僅是代碼從嵌套橫向變成了then縱向了而已啊?那我把回調(diào)函數(shù)放在外面賦值,不給里面不就行了?


function get(url) {
      var oReq = new XMLHttpRequest()
      // oReq.onload = function () {
      //   callback(JSON.parse(oReq.responseText))
      // }
      oReq.open('GET', url, true)
      oReq.send()

      return oReq // 返回這個(gè)是為了拿到 onload & oReq.responseText
}



var xhr = get('./data/1.json')
// 把異步callback注冊(cè)到Event Loop 的操作仍然是同步的,t同步的再慢,也比異步的要快.我就不相信會(huì)出現(xiàn),我xhr2 還沒(méi)執(zhí)行完,xhr 的 onload 回調(diào)函數(shù)就執(zhí)行了的情況!!!

// 同步代碼的執(zhí)行優(yōu)先權(quán)永遠(yuǎn)大于異步執(zhí)行代碼.
    xhr.onload = ()=>{
      console.log(JSON.parse(xhr.responseText))
    }
    var xhr2 = get('./data/2.json')
    xhr2.onload = () => {
      console.log(JSON.parse(xhr2.responseText))
    }
    var xhr3 = get('./data/3.json')
    xhr3.onload = () => {
      console.log(JSON.parse(xhr3.responseText))
    }    
// 對(duì)比
$.get('./data/users/1',function(data){
    $.get('./data/products/${data.userid}',function(data){
        $.get('./data/accountInfo/${data.productAccountInfo}',function(data){
            //......
        })
    })
})


// 無(wú)非就是把異步函數(shù)注冊(cè)代碼的步驟平移了出來(lái),僅此而已.


Promise的出現(xiàn)原因之一,就是為了讓我們不要在寫(xiě)那種 callback hell 那樣的代碼結(jié)構(gòu)了嗎?

Promise 的基本使用.

Promise 是解決啥的?
解決多個(gè)異步回調(diào)函數(shù)之間嵌套寫(xiě)法的問(wèn)題
意思就說(shuō),如果沒(méi)有異步,都是同步的代碼,就用不上Promise了

所以,用Promise主要是用在異步上.

可以Promise想象成一個(gè)異步操作的容器.
Promise承諾你,當(dāng)這個(gè)異步完成之后,不管成功還是失敗,只要你指定了對(duì)應(yīng)的回調(diào)函數(shù),我都會(huì)去執(zhí)行.

是不是感覺(jué)很廢?

  • 異步操作,我要裝在你里面去
  • 什么是成功,什么是失敗我也要告訴你.

但為了解決多個(gè)異步嵌套導(dǎo)致代碼橫向擴(kuò)展的問(wèn)題,咱們還是去用吧.畢竟逼格高一點(diǎn).

  • 首先我們需要實(shí)例化一個(gè)Promise對(duì)象(對(duì)Promise是一個(gè)構(gòu)造函數(shù))
new Promise()
  • 此構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù).
new Prmoise(function....)
  • 此函數(shù)參數(shù)包含兩個(gè)形參(reslove,reject)
new Promise(function(reslove,reject){})
  • 最后是完全體
new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})
  • 要把異步操作給 Promise ==> $.get('./data/users/1'
  • 告訴Promise啥是成功 ==> reslove(data)
  • 啥是失敗 ==> reject(data.error)

接著就是使用我們剛new出來(lái)的Promise.

既然是new出來(lái)的,我們就拿個(gè)對(duì)象去接受.

var p = new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})

我們?cè)诮oPromise傳參的時(shí)候,指定了 reslove & reject 兩個(gè)函數(shù)的形參,如何傳遞這兩個(gè)的實(shí)參呢?

p.then((data)=>{},(err)=>{})

使用實(shí)例對(duì)象的 then 方法,接受兩個(gè)參數(shù),順序是 then(resloveCallback,rejectCallback)

最后完整的使用,并發(fā)送請(qǐng)求.

var p = new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})

p.then((data)=>{
    console.log(data) // 數(shù)據(jù)請(qǐng)求成功
},(err)=>{
    console.log(err) // 數(shù)據(jù)請(qǐng)求失敗.
})

到這一步是不是很無(wú)聊?
無(wú)非就是提供了一個(gè)then方法,讓我們來(lái)指定成功和失敗的回調(diào)函數(shù)實(shí)參.....

Promise 的一些其他特性.

官方說(shuō)明:

Promise 是異步編程的一種解決方案匠襟,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大艰争。它由社區(qū)最早提出和實(shí)現(xiàn)互纯,ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn)隐锭,統(tǒng)一了用法刚照,原生提供了Promise對(duì)象衷掷。

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

  • 對(duì)象的狀態(tài)不受外界影響回论。Promise對(duì)象代表一個(gè)異步操作浩姥,有三種狀態(tài):pending(進(jìn)行中)挑随、fulfilled(已成功)和rejected(已失敗)勒叠。只有異步操作的結(jié)果兜挨,可以決定當(dāng)前是哪一種狀態(tài)膏孟,任何其他操作都無(wú)法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來(lái)拌汇,它的英語(yǔ)意思就是“承諾”柒桑,表示其他手段無(wú)法改變。
  • 一旦狀態(tài)改變噪舀,就不會(huì)再變幕垦,任何時(shí)候都可以得到這個(gè)結(jié)果。Promise對(duì)象的狀態(tài)改變傅联,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected先改。只要這兩種情況發(fā)生,狀態(tài)就凝固了蒸走,不會(huì)再變了仇奶,會(huì)一直保持這個(gè)結(jié)果,這時(shí)就稱為 resolved(已定型)比驻。如果改變已經(jīng)發(fā)生了该溯,你再對(duì)Promise對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果别惦。這與事件(Event)完全不同狈茉,事件的特點(diǎn)是,如果你錯(cuò)過(guò)了它掸掸,再去監(jiān)聽(tīng)氯庆,是得不到結(jié)果的。

根據(jù)文檔畫(huà)一張很無(wú)聊的圖.

Promise狀態(tài)變化

Promise 的基本套路

還是直接上代碼來(lái)的實(shí)際.


promise的then方法返回的仍然是個(gè)一個(gè) Promise對(duì)象

由于 Promise 的 then 方法,又返回了一個(gè) Promise 對(duì)象的類型,所以,Promise對(duì)象可以不停的then.

這就為 Promise 對(duì)象鏈?zhǔn)秸{(diào)用提供了基礎(chǔ).

function createReslovePromise() {
  return new Promise(function (reslove,reject) {
    setTimeout(function () {
      reslove(data)
    },1000)
  })
}

createReslovePromise()
  .then(() => {
    return 1
  })
  .then((data) => {
    console.log(data)
    return 2
  })
  .then((data) => {
    console.log(data)
  })


relax-2:測(cè)試 relax$ node Promise.js
1
2
relax-2:測(cè)試 relax$

圖解:

15440308685957.jpg

上一個(gè) thenreslove 的返回值,就是下一個(gè)thenreslove的參數(shù)

但是如果,,我在上一個(gè)Promise對(duì)象thenreslove
中,成功接受到了成功數(shù)據(jù)之后,就返回下一個(gè) Promise對(duì)象.

那么下一個(gè)then就是上一個(gè)返回的Promise對(duì)象的then了.

function createReslovePromise(stepName) {
  return new Promise(function (reslove,reject) {
    setTimeout(function () {
      reslove(stepName)
    },1000)
  })
}

createReslovePromise('step 01')
  .then((data) => {
    console.log(data) // 數(shù)據(jù)收到了,返回下一個(gè)Promise
    return createReslovePromise('step 02')
  })
  .then((data) => {
    console.log(data)
    return createReslovePromise('step 03')
  })
  .then((data) => {
    console.log(data)
  })

relax-2:測(cè)試 relax$ node Promise.js
step 01
step 02
step 03

圖解

15440311984996.jpg

基于上面這個(gè)then返回 Promise的特性,就可以完成鏈?zhǔn)降闹隙碌漠惒酱a結(jié)構(gòu)了.

使用Promise請(qǐng)求一個(gè)瞎編的邏輯

  • 首先請(qǐng)求 ./data/users/1 拿到id為1的用戶
  • 接著請(qǐng)求 ./data/preferences/{user.preferences} 在根據(jù)拿到用戶的 preferences 拿到用戶的偏好設(shè)置里面的bobbies
  • 最后根據(jù)用戶的preferences.hobbies 拿到用戶的愛(ài)好.../data/hobbies/{preferences.hobbies}
{
  "users":[
    {"id":1,"name":"張三","preferences":1},
    {"id":2,"name":"李四","preferences":2},
    {"id":3,"name":"王五","preferences":3},
    {"id":4,"name":"趙六","preferences":4}
  ],
  "preferences":[
    {"id":1,"hobbies":1},
    {"id":2,"hobbies":2},
    {"id":3,"hobbies":3},
    {"id":4,"hobbies":4}
  ],
  "hobbies":[
    {"id":1,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
    {"id":2,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
    {"id":3,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
    {"id":4,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]}
  ]
}

啟動(dòng)一個(gè) json-server 服務(wù)

json-server --watch db.json

服務(wù)啟動(dòng)成功

15440695886304.jpg

測(cè)試一下


15440696295064.jpg

使用傳統(tǒng)的 $.get 方式


 $.get('http://localhost:8000/users/1',function(data){
      let userInfo = data
      console.log(data)
      $.get(`http://localhost:8000/preferences/${userInfo.preferences}`,function(data){
        console.log(data)
        let userPreferences = data
        $.get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`,function(data){
          let userHobbies = data
          console.log(data)
          var hobbies = userHobbies.values && userHobbies.values.join(',')
          document.querySelector('h1').innerText = hobbies
        })
      })
    }) 
15440703375702.jpg

好像有callback hell回調(diào)地獄了.

使用原生XMLHttpRequest回調(diào)函數(shù)平移出來(lái)的方式


var xhr = get('http://localhost:8000/users/1')
xhr.onload = function () {
    let jsonData = JSON.parse(xhr.responseText)
    let userInfo = jsonData
    console.log(jsonData)
    var xhr2 = get(`http://localhost:8000/preferences/${userInfo.preferences}`)
      xhr2.onload = () => {
        let jsonData = JSON.parse(xhr2.responseText)
        let userPreferences = jsonData
        console.log(userPreferences)

        var xhr3 = get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`)
        xhr3.onload = () => {
          let jsonData = JSON.parse(xhr3.responseText)
          let userHobbies = jsonData
          var hobbies = userHobbies.values && userHobbies.values.join(',')
          document.querySelector('h1').innerText = hobbies
        }

      }
    }



    function get(url) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.send()

      return xhr
    }

突然發(fā)現(xiàn)問(wèn)題出來(lái)了,如果有依賴性的接口請(qǐng)求關(guān)系,不管怎么把回調(diào)函數(shù)移出來(lái),最后還是會(huì)被迫的寫(xiě)成 callback hell 的形式.

最后在使用 Promise

function pGet(url) {
      return new Promise(function (reslove, reject) {
        get(url, function (data) {
          reslove(data)
        })
      })
    }

    function get(url, callback) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.send()
      xhr.onload = function () {
        callback && typeof callback === 'function' && callback(JSON.parse(xhr.responseText))
      }
    }

    pGet('http://localhost:8000/users/1')
      .then(function(data){
        console.log(data)
        let userInfo = data
        return pGet(`http://localhost:8000/preferences/${userInfo.preferences}`)
      })
      .then((data)=>{
        console.log(data)
        let preferences = data
        return pGet(`http://localhost:8000/hobbies/${preferences.hobbies}`)
      })
      .then((data)=>{
        console.log(data)
        let hobbies = data
        var hobbiesStr = hobbies && hobbies.values instanceof Array && hobbies.values.join(',')
        document.querySelector('h1').innerText = hobbiesStr
      })

發(fā)現(xiàn)代碼最終是按照豎向的走向往下寫(xiě)的.
確實(shí)是避免了代碼橫向走的問(wèn)題.

使用Promise確實(shí)可以比較優(yōu)雅的寫(xiě)出有依賴關(guān)系接口方法的代碼層級(jí)結(jié)構(gòu).所以,它確實(shí)解決了callback hell寫(xiě)法的問(wèn)題.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扰付,一起剝皮案震驚了整個(gè)濱河市堤撵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羽莺,老刑警劉巖实昨,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盐固,居然都是意外死亡荒给,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門刁卜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)志电,“玉大人,你說(shuō)我怎么就攤上這事长酗∠保” “怎么了桐绒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵夺脾,是天一觀的道長(zhǎng)之拨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)咧叭,這世上最難降的妖魔是什么蚀乔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮菲茬,結(jié)果婚禮上吉挣,老公的妹妹穿的比我還像新娘。我一直安慰自己婉弹,他們只是感情好睬魂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著镀赌,像睡著了一般氯哮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上商佛,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天喉钢,我揣著相機(jī)與錄音,去河邊找鬼良姆。 笑死肠虽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玛追。 我是一名探鬼主播税课,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痊剖!你這毒婦竟也來(lái)了伯复?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤邢笙,失蹤者是張志新(化名)和其女友劉穎啸如,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體氮惯,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叮雳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妇汗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帘不。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖杨箭,靈堂內(nèi)的尸體忽然破棺而出寞焙,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布捣郊,位于F島的核電站辽狈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呛牲。R本人自食惡果不足惜刮萌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望娘扩。 院中可真熱鬧着茸,春花似錦、人聲如沸琐旁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灰殴。三九已至澎语,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間验懊,已是汗流浹背擅羞。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留义图,地道東北人减俏。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碱工,于是被迫代替她去往敵國(guó)和親娃承。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • title: promise總結(jié) 總結(jié)在前 前言 下文類似 Promise#then怕篷、Promise#resolv...
    JyLie閱讀 12,241評(píng)論 1 21
  • Promiese 簡(jiǎn)單說(shuō)就是一個(gè)容器历筝,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果,語(yǔ)法上說(shuō)廊谓,Pr...
    雨飛飛雨閱讀 3,358評(píng)論 0 19
  • 異步編程對(duì)JavaScript語(yǔ)言太重要梳猪。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的,如果沒(méi)有異步編程蒸痹,根本...
    呼呼哥閱讀 7,311評(píng)論 5 22
  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案春弥,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,706評(píng)論 1 56
  • 讀《運(yùn)營(yíng)之光》有感 我一直覺(jué)得讀書(shū)筆記的精髓是通過(guò)閱讀一本書(shū),除了學(xué)習(xí)書(shū)中的知識(shí)體系叠荠,還能夠結(jié)合自身情況進(jìn)行思考和...
    貓男囈語(yǔ)閱讀 340評(píng)論 0 0