Promise 使用詳解

查看了阮一峰ES6 借鑒里面的內(nèi)容總結(jié)

什么是 Promise

Promise 是異步編程的一種解決方案, 沒有 Promise 之前, 使用的是 回調(diào)函數(shù)和事件

回調(diào)地獄

  • 以前沒有 Promise 時, 我們使用回調(diào)來解決異步問題, 但一旦回調(diào)的次數(shù)變多, 回調(diào)里面嵌套回調(diào), 代碼就變得難以維護(hù)
// 當(dāng)一個數(shù)據(jù)返回, 使用結(jié)果來請求另一個數(shù)據(jù), 等待結(jié)果再次請求其它數(shù)據(jù)... , 無窮盡也
<script>
$.ajax({
  type: 'get',
  url: './user.json',
  success: (res) => {
    const { id } = res
    
    $.ajax({
      type: 'get',
      url: './tel.json',
      success: res => {
          const { tel } = res
          
          $.ajax({
            type: 'get',
            url: './tel.json',
            success: res => {
                // TODO: 請求...
            }
          })
      }
    })
  }
})
</script>

ES6 -> Promise

Promise 解決了上面的回調(diào)地獄

  1. Promise 特點(diǎn)

    • 對象的狀態(tài)不受外界影響
    • 一旦狀態(tài)改變, 就不會再變
  2. Promise 基本使用

/**
 * 接受一個函數(shù)作為參數(shù)
 * 參數(shù)函數(shù) 有兩個參數(shù): resolve, reject
 */
new Promise((resolve, reject) => {})

// 得到結(jié)果
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
  1. Promise 狀態(tài)

    • 看到上面 new Promise 返回結(jié)果里有一個[[PromsieState]]: pending
    • Promise 有三種狀態(tài): pending(進(jìn)行中) fulfilled(已成功) rejected(以失敗)
  2. 改變Promise 狀態(tài)

    上面我們知道 Promise 特點(diǎn)就是: 對象狀態(tài)不受外界影響, 那我們應(yīng)該怎么改變 Promise 的狀態(tài)呢?

    // 只有異步操作的結(jié)果碍现,可以決定當(dāng)前是哪一種狀態(tài)幅疼,任何其他操作都無法改變這個狀態(tài)
    
    // Promise 狀態(tài)從 pending -> fulfilled
    const p1 = new Promise((resolve, reject) => resolve())
    console.log(p1) // [[PromiseState]]: "fulfilled"
    
    
    // Promise 狀態(tài)從 pending -> rejected
    const p2 = new Promise((resolve, reject) => reject())
    console.log(p2) // [[PromiseState]]: "rejected"
    
    // Promise 狀態(tài)一旦改變. 就不會再變
    const p3 = new Promise((resolve, reject) => {
      resolve()
      reject()
    })
    console.log(p3) // [[PromiseState]]: "fulfilled"
    
    const p4 = new Promise((resolve, reject) => {
      reject()
      resolve()
    })
    console.log(p4) // [[PromiseState]]: "rejected"
    
  3. 得到 Promise 的結(jié)果

    // 得到成功的結(jié)果
    const p1 = new Promise((resolve, reject) => {
      resolve('成功了')
    })
    console.log(p1) // [[PromiseResult]]: "成功了"
    p1.then(res => console.log(res))  // 成功了
    
    // 得到失敗的結(jié)果
    const p2 = new Promise((resolve, reject) => {
      reject('失敗了')
    })
    console.log(p2) // [[PromiseResult]]: "失敗了"
    p2.catch(err => console.log(err)) // 失敗了
    

使用 Promise 方法

  • then 方法
  1. Promise 實例具有then方法,也就是說昼接,then方法是定義在原型對象Promise.prototype上的
    • then方法的第一個參數(shù)是resolved狀態(tài)的回調(diào)函數(shù)爽篷,第二個參數(shù)是rejected狀態(tài)的回調(diào)函數(shù),它們都是可選的
    • then方法返回的是一個新的Promise實例, 狀態(tài)是pending 可以采用鏈?zhǔn)綄懛?/li>
// then 接收兩個參數(shù), 都是可選的, 參數(shù)都是函數(shù), 返回值也是 Promise 實例
const p1 = new Promise((resolve, reject) => {
    // 實參
    resolve('成功')
})

// [[PromiseState]]: "fulfilled"

// resolve 實參 傳給 第一個函數(shù)的形參
p1.then((res) => {
    console.log(res, '成功時的回調(diào)') // 成功 成功時的回調(diào)
}, (err) => {
    console.log(err, '失敗時的回調(diào)')
})

const p2 = new Promise((resolve, reject) => {
    reject('失敗')
})

p2.then((res) => {
    console.log(res, '成功時的回調(diào)')
}, (err) => {
    console.log(err, '失敗時的回調(diào)') // 失敗 失敗時的回調(diào)
})
  1. Promise 狀態(tài)不改變 不會執(zhí)行 .then() 里面的方法
new Promise((resolve, reject) => {
    
}).then((res) => {
    console.log(res, '成功時的回調(diào)')
}, (err) => {
    console.log(err, '失敗時的回調(diào)')
})

// 返回結(jié)果 pending
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined

// 兩次 then, resolve()
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功時的回調(diào)') // undefined '成功時的回調(diào)'
}, (err) => {
    console.log(err, '失敗時的回調(diào)')
}).then((res) => {
    console.log(res, '成功2') // undefined '成功2'
}, (err) => {
    console.log(err, '失敗2')
})

// 在 then 成功里面 return
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功時的回調(diào)') // undefined '成功時的回調(diào)'
    return 'yym'
}, (err) => {
    console.log(err, '失敗時的回調(diào)')
}).then((res) => {
    console.log(res, '成功2') // yym 成功2
}, (err) => {
    console.log(err, '失敗2')
})

// 返回結(jié)果 fulfilled
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined

// 進(jìn)入 失敗 的回調(diào)
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功時的回調(diào)')
    throw new Error('出錯了')
}, (err) => {
    console.log(err, '失敗時的回調(diào)')
}).then((res) => {
    console.log(res, '成功2')
}, (err) => {
    console.log(err, '失敗2') // Error: 出錯了, '失敗2'
})

  • catch 方法
  1. Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的別名慢睡,用于指定發(fā)生錯誤時的回調(diào)函數(shù)
// catch 中的參數(shù)函數(shù)在什么時候執(zhí)行

// 1. 當(dāng) promise 的狀態(tài)從 pending -> rejected
new Promise((resolve, reject) => {
  reject()
}).catch(() => {
  console.log('失敗了...') // 失敗了...
})

// 2. 當(dāng) promise 代碼執(zhí)行出現(xiàn)錯誤
new Promise((resolve, reject) => {
    throw new Error('錯誤')
}).catch(() => {
    console.log('失敗了...')
})
  1. [引用阮一峰ES6 promise] Promise 對象的錯誤具有“冒泡”性質(zhì)逐工,會一直向后傳遞,直到被捕獲為止漂辐。也就是說钻弄,錯誤總是會被下一個catch語句捕獲
// 一共有三個 Promise 對象:一個由`getJSON()`產(chǎn)生,兩個由`then()`產(chǎn)生者吁。它們之中任何一個拋出的錯誤窘俺,都會被最后一個`catch()`捕獲

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 處理前面三個Promise產(chǎn)生的錯誤
});
  1. catch 最佳實踐
    • 下面代碼第二種寫法可以捕獲前面then方法執(zhí)行中的錯誤,也更接近同步的寫法(try/catch)复凳。因此瘤泪,建議總是使用catch()方法,而不使用then()方法的第二個參數(shù)

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

  • finally() 方法
  1. finally()方法用于指定不管 Promise 對象最后狀態(tài)如何育八,都會執(zhí)行的操作
  2. finally方法的回調(diào)函數(shù)不接受任何參數(shù)对途,這意味著沒有辦法知道,前面的 Promise 狀態(tài)到底是fulfilled還是rejected髓棋。這表明实檀,finally方法里面的操作惶洲,應(yīng)該是與狀態(tài)無關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
// finally 實現(xiàn)
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

Promise 其它使用

  • Promise.all
  1. Promise.all()方法用于將多個 Promise 實例膳犹,包裝成一個新的 Promise 實例
// 接收一個數(shù)組為參數(shù)
Promise.all([a, b, c]).then(() => {})

// a b c 都是 promise 實例, 都從 pending -> fulfilled, Promise.all 才會返回成功 fulfilled
// 只要有一個 rejected, Promise.all 就會失敗


const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve) => resolve(3))

Promise.all([p1, p2, p3]).then(res => {
    console.log(res, 'res是個數(shù)組')
})
// [1, 2, 3] 'res是個數(shù)組'

const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.all([p1, p2, p3]).then(res => {
    console.log(res, 'res是個數(shù)組')
}).catch(reason => {
    console.log(reason, 'reason') // 3 'reason'
})
  • Promise.allSettled()

有時候恬吕,我們希望等到一組異步操作都結(jié)束了,不管每一個操作是成功還是失敗须床,再進(jìn)行下一步操作

  1. Promise.allSettled()方法接受一個數(shù)組作為參數(shù)铐料,數(shù)組的每個成員都是一個 Promise 對象,并返回一個新的 Promise 對象豺旬。只有等到參數(shù)數(shù)組的所有 Promise 對象都發(fā)生狀態(tài)變更(不管是fulfilled還是rejected)钠惩,返回的 Promise 對象才會發(fā)生狀態(tài)變更
const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.allSettled([p1, p2, p3]).then(res => {
    console.log(res)
})

// 返回
[
    {status: 'fulfilled', value: 1}
    {status: 'fulfilled', value: 2}
    {status: 'rejected', reason: 3}
]

// 使用 Promise.all 實現(xiàn) Promise.allSettled 效果
const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

x = promiseList => promiseList.map(promise => promise.then(result => ({status: 'ok', value: result}),reason =>  ({status: 'not ok', reason}) ))

  Promise.all(x([p1, p2, p3])).then(res => console.log(res, 'res...'))
  // {status: 'not ok', reason: 1}
  // {status: 'ok', value: 2}
  // {status: 'not ok', reason: 3}
  • Promise.race()
  1. Promise.race()方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例
const p = Promise.race([p1, p2, p3]);
// 只要`p1`族阅、`p2`篓跛、`p3`之中有一個實例率先改變狀態(tài),`p`的狀態(tài)就跟著改變坦刀。那個率先改變的 Promise 實例的返回值举塔,就傳遞給`p`的回調(diào)函數(shù)

const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.race([p1, p2, p3]).then(res => {
    console.log(res, 'res')
}).catch(reason => {
    console.log(reason, 'reason') // 1, p1 先改變狀態(tài), 是 Promise 實例的返回值
})
  • Promise.any()

方法接受一組 Promise 實例作為參數(shù),包裝成一個新的 Promise 實例返回

  1. 只要參數(shù)實例有一個變成fulfilled狀態(tài)求泰,包裝實例就會變成fulfilled狀態(tài);如果所有參數(shù)實例都變成rejected狀態(tài)计盒,包裝實例就會變成rejected狀態(tài)
// `Promise.any()`不會因為某個 Promise 變成`rejected`狀態(tài)而結(jié)束
const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.any([p1, p2, p3]).then(res => {
    console.log(res) // 2 成功的
})

---

const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve, reject) => reject(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.any([p1, p2, p3]).then(res => {
    console.log(res)
})
// 返回 rejected
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: AggregateError: All promises were rejected

  1. 就是Promise.any()不會因為某個 Promise 變成rejected狀態(tài)而結(jié)束渴频,必須等到所有參數(shù) Promise 變成rejected狀態(tài)才會結(jié)束

Promise.resolve()

有時需要將現(xiàn)有對象轉(zhuǎn)為 Promise 對象,Promise.resolve()方法就起到這個作用

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

    • 如果參數(shù)是 Promise 實例北启,那么Promise.resolve將不做任何修改卜朗、原封不動地返回這個實例
  2. 參數(shù)是一個thenable對象

    • thenable對象指的是具有then方法的對象
    • Promise.resolve()方法會將這個對象轉(zhuǎn)為 Promise 對象,然后就立即執(zhí)行thenable對象的then()方法
    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    let p1 = Promise.resolve(thenable);
    p1.then(function (value) {
      console.log(value);  // 42
    });
    
  3. 參數(shù)是一個原始值

    • Promise.resolve()方法返回一個新的 Promise 對象咕村,狀態(tài)為resolved
    const p = Promise.resolve('Hello');
    
    p.then(function (s) {
      console.log(s)
    });
    // Hello
    
  4. 不帶有任何參數(shù)

    • Promise.resolve()方法允許調(diào)用時不帶參數(shù)场钉,直接返回一個resolved狀態(tài)的 Promise 對象
    // 立即`resolve()`的 Promise 對象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時執(zhí)行懈涛,而不是在下一輪“事件循環(huán)”的開始時
    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.resolve().then(function () {
      console.log('two');
    });
    
    console.log('one');
    
    // one
    // two
    // three
    

Promise.reject()

Promise.reject(reason)方法也會返回一個新的 Promise 實例逛万,該實例的狀態(tài)為rejected

const p = Promise.reject('出錯了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

Promise.try()

Promise.try為所有操作提供了統(tǒng)一的處理機(jī)制,所以如果想用then方法管理流程批钠,最好都用Promise.try包裝一下

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}
// 上面的方法看起來很笨拙,  可以統(tǒng)一用`promise.catch()`捕獲所有同步和異步的錯誤
Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)
// `Promise.try`就是模擬`try`代碼塊宇植,就像`promise.catch`模擬的是`catch`代碼塊
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市埋心,隨后出現(xiàn)的幾起案子指郁,更是在濱河造成了極大的恐慌,老刑警劉巖拷呆,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闲坎,死亡現(xiàn)場離奇詭異疫粥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腰懂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門梗逮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悯恍,你說我怎么就攤上這事库糠。” “怎么了涮毫?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵瞬欧,是天一觀的道長。 經(jīng)常有香客問我罢防,道長艘虎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任咒吐,我火速辦了婚禮野建,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恬叹。我一直安慰自己候生,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布绽昼。 她就那樣靜靜地躺著唯鸭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硅确。 梳的紋絲不亂的頭發(fā)上目溉,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音菱农,去河邊找鬼缭付。 笑死,一個胖子當(dāng)著我的面吹牛循未,可吹牛的內(nèi)容都是我干的陷猫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼的妖,長吁一口氣:“原來是場噩夢啊……” “哼烙丛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羔味,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤河咽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赋元,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忘蟹,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡飒房,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了媚值。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狠毯。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖褥芒,靈堂內(nèi)的尸體忽然破棺而出嚼松,到底是詐尸還是另有隱情,我是刑警寧澤锰扶,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布献酗,位于F島的核電站,受9級特大地震影響坷牛,放射性物質(zhì)發(fā)生泄漏罕偎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一京闰、第九天 我趴在偏房一處隱蔽的房頂上張望颜及。 院中可真熱鬧,春花似錦蹂楣、人聲如沸俏站。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肄扎。三九已至,卻和暖如春施戴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赞哗。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆雾,地道東北人肪笋。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像度迂,于是被迫代替她去往敵國和親藤乙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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