摘要:本文僅限瀏覽器環(huán)境測(cè)試,環(huán)境可能會(huì)不一致狀態(tài)一個(gè)實(shí)例只能處于三種狀態(tài)中的一種。每次創(chuàng)建的實(shí)例都會(huì)處于狀態(tài),并且只能由變?yōu)榛驙顟B(tài)??梢哉J(rèn)為在實(shí)現(xiàn)里與中的都為解決程序。
前言
Promise作為ES6極為重要的一個(gè)特性,將我們從無(wú)限的回調(diào)地獄中解脫出來(lái),變?yōu)殒準(zhǔn)降木帉懟卣{(diào),大大提高的代碼的可讀性。
使用Promise是極為簡(jiǎn)單的,但只停留在會(huì)使用階段還是會(huì)讓我們不知不覺(jué)踩到一些坑的。本文會(huì)結(jié)合Promise/A+規(guī)范與示例來(lái)深入學(xué)習(xí)Promise
本文較長(zhǎng),例子很多,末尾有一些應(yīng)用 ^_^
Promise兼容性
Promise作為是瀏覽器的內(nèi)置函數(shù)對(duì)象,除IE不支持外所有主流版本的瀏覽器都是支持的,如不需支持IE可以在不引入polyfill的情況下放心食用
作用
js是單線程的,異步是通過(guò)Event Loop來(lái)實(shí)現(xiàn)的,那么需要一種更加友好的方式來(lái)實(shí)現(xiàn)我們的異步操作,而不是無(wú)限的回調(diào)嵌套
// no promise callback(() => { callback(() => { callback(() => { ... }) }) }) // with promise new Promise((resolve, reject) => { }).then(() => { }).then(() => { }).then(() => { })
API
先來(lái)簡(jiǎn)單看一下Promise提供的一些API(注意區(qū)分靜態(tài)方法與實(shí)例方法)
構(gòu)造函數(shù)Promise
Promise是一個(gè)構(gòu)造函數(shù),可以通過(guò)new操作符來(lái)創(chuàng)建一個(gè)Promise實(shí)例
let promise = new Promise((resolve, reject) => { resolve(1) })
靜態(tài)方法
Promise.resolve
Promise.reject
Promise.all
Promise.race
實(shí)例方法
Promise.prototype.then
Promise.prototype.catch
結(jié)合規(guī)范與樣例
規(guī)范
官方英文原版規(guī)范
tips:
規(guī)范是規(guī)范,實(shí)現(xiàn)是實(shí)現(xiàn),實(shí)現(xiàn)是按照規(guī)范來(lái)實(shí)現(xiàn)的,但不一定完全等同于規(guī)范。
本文僅限瀏覽器環(huán)境測(cè)試,node環(huán)境可能會(huì)不一致
狀態(tài)
一個(gè)Promise實(shí)例只能處于pending,fulfilled,rejected三種狀態(tài)中的一種。每次創(chuàng)建的promise實(shí)例都會(huì)處于pending狀態(tài),并且只能由pending變?yōu)?b>fulfilled或reject狀態(tài)。一旦處于fulfilled或rejected狀態(tài)無(wú)論進(jìn)行什么操作都不會(huì)再次變更狀態(tài)(規(guī)范2.1)
// 創(chuàng)建一個(gè)處于pending狀態(tài)的promise實(shí)例 let promise1 = new Promise((resolve, reject) => { }) // 調(diào)用resolve()會(huì)使promise從pending狀態(tài)變?yōu)閒ulfilled狀態(tài) let promise2 = new Promise((resolve, reject) => { resolve(1) }) // 調(diào)用reject()會(huì)使promise從pending狀態(tài)變?yōu)閞ejected狀態(tài) let promise3 = new Promise((resolve, reject) => { reject(1) }) // 無(wú)論如何更改resolve與reject的執(zhí)行順序,promise4始終只會(huì)處于先調(diào)用后轉(zhuǎn)換的狀態(tài)(狀態(tài)一旦變?yōu)閒ulfilled或rejected后不會(huì)再次改變) let promise4 = new Promise((resolve, reject) => { resolve(1) reject(1) })
then
參數(shù)
實(shí)例方法then的回調(diào)函數(shù)接受兩個(gè)參數(shù),onFulfilled和onRejected,都為可選參數(shù)(規(guī)范2.2.1)
// onFulfilled會(huì)在狀態(tài)變?yōu)閒ulfilled后調(diào)用,onFulfilled參數(shù)value為resolve的第一個(gè)參數(shù),onRejected同理。(規(guī)范2.2.2與2.2.3) let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) promise.then((value) => { console.log(value) // 1秒后輸出1 }, (reason) => { }) // 可被同一個(gè)promise多次調(diào)用(規(guī)范2.2.6) promise.then() promise.then((value) => { console.log(value) // 1秒后輸出1 })
返回值
then方法必須返回一個(gè)promise實(shí)例(規(guī)范2.2.7)
假定 promise2 = promise1.then(onFulfilled, onRejected)
var a = new Promise((resolve, reject) => { resolve(1) }) var b = new Promise((resolve, reject) => { reject(1) }) // 如果```onFulfilled```或```onRejected```返回了一個(gè)value ```x```,運(yùn)行promise解決程序(下一節(jié)), [[Resolve]](promise2, x) (規(guī)范2.2.7.1) var a1 = a.then(function onFulfilled(value) { return 1 }, function onRejected (reason) { }) var b1 = b.then(function onFulfilled(value) { }, function onRejected (reason) { return 1 }) // 如果```onFulfilled```或```onRejected```拋出了一個(gè)exception ```e```, ```promise2```必須以e作為reason rejected (規(guī)范2.2.7.2) // 故下方a2 b2 都為狀態(tài)是rejected的promise實(shí)例 var a2 = a.then(function onFulfilled(value) { throw Error("test") }, function onRejected (reason) { }) var b2 = b.then(function onFulfilled(value) { }, function onRejected (reason) { throw Error("test") }) // 如果promise1處于fulfilled狀態(tài)并且onFulfilled不是一個(gè)函數(shù),那么promise2會(huì)以與promise1具有相同value和相同的狀態(tài), 但是與promise1并不是同一Promise實(shí)例;若為rejected狀態(tài)以此類推 var a3 = a.then() a3 === a // false var b3 = b.then() b3 === b // false
解決程序resolve
在規(guī)范中, promise解決程序是一個(gè)以promise和value作為輸入的抽象操作,我們表示為[[Resolve]](promise, x)。
可以認(rèn)為在實(shí)現(xiàn)里Promise.resolve()與new Promise((resolve, reject) => {})中的resolve都為解決程序。規(guī)范中x對(duì)應(yīng)實(shí)現(xiàn)中resolve的第一個(gè)參數(shù),規(guī)范中promise在實(shí)現(xiàn)中等價(jià)于當(dāng)前promise
可以理解為Promise.resolve(x)與new Promise(resolve) => {resolve(x)}等價(jià)
var c = new Promise((resolve, reject) => { resolve(1) }) var d = new Promise((resolve, reject) => { reject(1) }) var e = new Promise((resolve, reject) => { setTimeout(() => { resolve("5s后resolve") }, 5000) }) // 在返回值章節(jié)我們了解到,onFulfilled如果為函數(shù)在不拋出錯(cuò)誤的情況下,會(huì)調(diào)用解決程序處理返回值x // 如果x是promise, 采納他的狀態(tài)(注意,但then的返回值并不與x相等)(規(guī)范2.3.2) var c1 = Promise.resolve().then(function onFulfilled() { return c }) c1 === c // false // pending狀態(tài)下的,只要e狀態(tài)變?yōu)椋琫1也會(huì)改變(規(guī)范2.3.2.1) var e1 = Promise.resolve().then(function onFulfilled () { return e }) var c2 = Promise.resolve() // 如果promise和x是相同的對(duì)象, 使用TypeError作為reason reject promise(規(guī)范2.3.1) var c3 = c2.then(function onFulfilled () { return c3 })
概念:如果一個(gè)函數(shù)或者對(duì)象具有then屬性,那么叫做thenable(例: { then: function(){} })
// 如果x是thenable 但x.then不是一個(gè)函數(shù)或者對(duì)象,直接返回狀態(tài)為fulfilled,value為x的promise實(shí)例(規(guī)范2.3.3.4) var c4 = c1.then(function onFulfilled () { return {} }) var c5 = c1.then(function onFulfilled () { return { then: 2 } }) // 如果x是thenable 并且x.then是一個(gè)函數(shù),那么就會(huì)調(diào)用這個(gè)then方法執(zhí)行,并且兩個(gè)參數(shù)resolve與reject擁有改變返回的promise狀態(tài)的權(quán)利,會(huì)按解決程序處理,如果不調(diào)用兩個(gè)參數(shù)方法,則返回的promise為pending狀態(tài),值得一提的是,調(diào)用then方法的時(shí)候回以返回對(duì)象x作為then的this綁定調(diào)用(規(guī)范 2.3.3.3) var c6 = c1.then(function onFulfilled () { return { test: "test", then: function (resolve, reject) { console.log(this.test) resolve("thenable") } } }) var c7 = c1.then(function onFulfilled () { function x () {} x.test = "test" x.then = function (resolve, reject) { console.log(this.test) resolve("thenable") } return x }) var c8 = c1.then(function onFulfilled () { return { test: "test", then: function (resolve, reject) { console.log(this.test) reject("thenable") } } }) // 返回pending狀態(tài)的promise var c9 = c1.then(function onFulfilled () { return { then: function () {} } }) // 如果x不是對(duì)象也不是函數(shù),則返回狀態(tài)為fulfilled的promise,value為x var c10 = c1.then(function onFulfilled () { return "hehe" })
綜上可以總結(jié)幾點(diǎn)
1. ```new Promise, promise.then, Promise.resolve()```都會(huì)返回promise實(shí)例 2. Promise狀態(tài)一旦改變不可再更改 3. ```then```方法返回對(duì)象的不同會(huì)導(dǎo)致不同的結(jié)果,如果意外返回了thenable有可能會(huì)造成意想不到的結(jié)果應(yīng)用
封裝一個(gè)異步請(qǐng)求
var promiseXHR = new Promise((resolve, reject) => { var xhr = new XMLHttpRequest() xhr.open("GET", "http://www.baidu.com", true) xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { resolve(xhr.response) } else { reject() } } xhr.send(data) })
同時(shí)請(qǐng)求按序處理
// 假設(shè)有5個(gè)章節(jié)的數(shù)據(jù),我們需要分別獲取并讀取到c中,利用promise和es6數(shù)組api我們可以寫出簡(jiǎn)潔優(yōu)雅的代碼完成這個(gè)需求 var list = ["Chapter1", "Chapter2", "Chapter3", "Chapter4", "Chapter5"] var getData = function (key) { return new Promise((resolve, reject) => { // 模擬一些返回時(shí)間不相同的異步操作 setTimeout(() => { resolve(key) }, 4000 * Math.random()) }) } var c = "" list.map(i => getData(i)) .reduce((accumulator, current) => { console.log(accumulator) return accumulator.then(() => { return current }).then(value => { c+= value }) }, Promise.resolve(""))
catch
明明在我們?nèi)粘9ぷ髦谐S玫降腸atch方法,為什么到現(xiàn)在還一點(diǎn)都沒(méi)有提到呢?
因?yàn)閏atch方法就是then方法封裝出來(lái)的語(yǔ)法糖而已,因?yàn)槿绻幌氩东@錯(cuò)誤,還每次都要書寫then的第一個(gè)參數(shù),真的是麻煩至極,現(xiàn)在我們來(lái)寫一個(gè)自己的catch
Promise.prototype.mycatch = function (cb) { return this.then(1, function onRejected (reason) { return cb(reason) }) }
// 用到了規(guī)范中如果onFulfilled不是一個(gè)函數(shù),則忽略,并使用原先promise狀態(tài)與value
finally
有的瀏覽器實(shí)現(xiàn)了finally,而有的并沒(méi)有,從需求上來(lái)講finally只不過(guò)是最終要執(zhí)行一個(gè)函數(shù),我們需要把應(yīng)該有的狀態(tài)或者異常都繼續(xù)傳遞,不受其影響。執(zhí)行的函數(shù)與promise的value無(wú)任何關(guān)系
Promise.prototype.myfinally = function (cb) { return this.then(function onFulfilled (value) { return Promise.resolve(cb()).then(() => value) }, function onRejected (reason) { return Promise.resolve(cb()).then(() => { throw reason }) }) }后記
通過(guò)閱讀規(guī)范并寫demo進(jìn)行驗(yàn)證測(cè)試,對(duì)Promise的理解更加深入了,也能更好的使用它了
如果喜歡可以star一下,會(huì)不斷更新github地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/94387.html
摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類中文書籍收錄并推薦地址,以后在倉(cāng)庫(kù)里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡(jiǎn)介現(xiàn)在,越來(lái)越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說(shuō)明 2017-12-14 我發(fā)了一篇文章《沒(méi)用過(guò)Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車的還坐過(guò)站了。大家可以很...
摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類中文書籍收錄并推薦地址,以后在倉(cāng)庫(kù)里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡(jiǎn)介現(xiàn)在,越來(lái)越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說(shuō)明 2017-12-14 我發(fā)了一篇文章《沒(méi)用過(guò)Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車的還坐過(guò)站了。大家可以很...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開篇 前端開發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...
閱讀 933·2021-09-07 09:58
閱讀 1484·2021-09-07 09:58
閱讀 2869·2021-09-04 16:40
閱讀 2501·2019-08-30 15:55
閱讀 2404·2019-08-30 15:54
閱讀 1364·2019-08-30 15:52
閱讀 423·2019-08-30 10:49
閱讀 2598·2019-08-29 13:21