目錄
- 說到Promise就不得不說道說道這 —— 回調(diào)地獄
- Promise —— 解決回調(diào)地獄
- Promise語法規(guī)范
- Promise的狀態(tài)
- Promise基本用法
- Promise初體驗(yàn)
- Promise的本質(zhì)
- Promise鏈?zhǔn)秸{(diào)用
- 常見誤區(qū)
- 鏈?zhǔn)秸{(diào)用的理解
- Promise.prototype.then()
- Promise異常處理
- then中回調(diào)的onRejected方法
- Promise.prototype.catch()(推薦)
- .catch形式和前面then里面的第二個(gè)參數(shù)的形式菩颖,兩者異常捕獲的區(qū)別:
- 全局對象上的unhandledrejection事件
- Promise靜態(tài)方法
- 類型轉(zhuǎn)換 —— Promise.resolve()
- 使用場景
- Promise.reject()
- 數(shù)據(jù)聚合 —— Promise.all()
- 競爭 —— Promise.race()
- 類型轉(zhuǎn)換 —— Promise.resolve()
- Promise執(zhí)行時(shí)序 —— 宏任務(wù) vs 微任務(wù)
- 深度剖析:手寫一個(gè)Promise源碼
- ES6-ES10學(xué)習(xí)版圖
說到Promise就不得不說道說道這 —— 回調(diào)地獄
a => b => c => d
回調(diào)層數(shù)越深酵紫,那么回調(diào)的維護(hù)成本越高
//異步加載函數(shù)
function loadScript (src, callback) {
let script = document.createElement('script')
script.src = src
script.onload = () => {
callback()
}
document.head.append(script)
}
function test () {
console.log('test')
}
loadScript('./1.js', test)
// 1
// test
如果有三個(gè)這樣的方式回調(diào)
function loadScript (src, callback) {
let script = document.createElement('script')
script.src = src
script.onload = () => {
callback(src)
}
document.head.append(script)
}
function test (name) {
console.log(name)
}
loadScript('./1.js', function (script) {
console.log(script)
loadScript('./2.js', function (script) {
console.log(script)
loadScript('./3.js', function (script) {
console.log(script)
//...
})
})
})
// 1
// ./1.js
// 2
// ./2.js
// 3
// ./3.js
Promise —— 解決回調(diào)地獄
雖然回調(diào)函數(shù)是所有異步編程方案的根基应闯。但是如果我們直接使用傳統(tǒng)回調(diào)方式去完成復(fù)雜的異步流程躁劣,就會無法避免大量的回調(diào)函數(shù)嵌套。導(dǎo)致回調(diào)地獄的問題躬存。
為了避免這個(gè)問題犁柜。CommonJS
社區(qū)提出了Promise
的規(guī)范,ES6
中稱為語言規(guī)范疯兼。
Promise
是一個(gè)對象,用來表述一個(gè)異步任務(wù)執(zhí)行之后是成功還是失敗贫途。
Promise語法規(guī)范
new Promise( function(resolve, reject) {…} );
new Promise(fn)
返回一個(gè)Promise
對象- 在
fn
中指定異步等處理
- 處理結(jié)果正常的話吧彪,調(diào)用
resolve
(處理結(jié)果值)- 處理結(jié)果錯(cuò)誤的話,調(diào)用
reject
(Error
對象)
Promise的狀態(tài)
Promise
內(nèi)部是有狀態(tài)的 (pending丢早、fulfilled姨裸、rejected) ,Promise
對象根據(jù)狀態(tài)來確定執(zhí)行哪個(gè)方法怨酝。Promise
在實(shí)例化的時(shí)候狀態(tài)是默認(rèn)pending
的傀缩,
- 當(dāng)異步操作是完成的,狀態(tài)會被修改為
fulfilled
- 如果異步操作遇到異常农猬,狀態(tài)會被修改為
rejected
赡艰。
無論修改為哪種狀態(tài),之后都是不可改變的斤葱。
Promise基本用法
返回resolve
const promise = new Promise((resolve, reject) => {
resolve(100)
})
promise.then((value) => {
console.log('resolved', value) // resolve 100
},(error) => {
console.log('rejected', error)
})
返回reject
const promise = new Promise((resolve, reject) => {
reject(new Error('promise rejected'))
})
promise.then((value) => {
console.log('resolved', value)
},(error) => {
console.log('rejected', error)
// rejected Error: promise rejected
// at E:\professer\lagou\Promise\promise-example.js:4:10
// at new Promise (<anonymous>)
})
即便promise
中沒有任何的異步操作慷垮,then
方法的回調(diào)函數(shù)仍然會進(jìn)入到事件隊(duì)列中排隊(duì)揖闸。
Promise初體驗(yàn)
使用Promise
去封裝一個(gè)ajax
的案例
function ajax (url) {
return new Promise((resolve, rejects) => {
// 創(chuàng)建一個(gè)XMLHttpRequest對象去發(fā)送一個(gè)請求
const xhr = new XMLHttpRequest()
// 先設(shè)置一下xhr對象的請求方式是GET,請求的地址就是參數(shù)傳遞的url
xhr.open('GET', url)
// 設(shè)置返回的類型是json料身,是HTML5的新特性
// 我們在請求之后拿到的是json對象汤纸,而不是字符串
xhr.responseType = 'json'
// html5中提供的新事件,請求完成之后(readyState為4)才會執(zhí)行
xhr.onload = () => {
if(this.status === 200) {
// 請求成功將請求結(jié)果返回
resolve(this.response)
} else {
// 請求失敗,創(chuàng)建一個(gè)錯(cuò)誤對象芹血,返回錯(cuò)誤文本
rejects(new Error(this.statusText))
}
}
// 開始執(zhí)行異步請求
xhr.send()
})
}
ajax('/api/user.json').then((res) => {
console.log(res)
}, (error) => {
console.log(error)
})
Promise的本質(zhì)
本質(zhì)上也是使用回調(diào)函數(shù)的方式去定義異步任務(wù)結(jié)束后所需要執(zhí)行的任務(wù)贮泞。這里的回調(diào)函數(shù)是通過then
方法傳遞過去的
Promise鏈?zhǔn)秸{(diào)用
常見誤區(qū)
- 嵌套使用的方式是使用
Promise
最常見的誤區(qū)。要使用promise
的鏈?zhǔn)秸{(diào)用的方法盡可能保證異步任務(wù)的扁平化幔烛。
鏈?zhǔn)秸{(diào)用的理解
-
promise
對象then
方法隙畜,返回了全新的promise
對象∷当矗可以再繼續(xù)調(diào)用then
方法议惰,如果return
的不是promise
對象,而是一個(gè)值乡恕,那么這個(gè)值會作為resolve
的值傳遞言询,如果沒有值,默認(rèn)是undefined
- 后面的
then
方法就是在為上一個(gè)then
返回的Promise
注冊回調(diào) - 前面
then
方法中回調(diào)函數(shù)的返回值會作為后面then
方法回調(diào)的參數(shù) - 如果回調(diào)中返回的是
Promise
傲宜,那后面then
方法的回調(diào)會等待它的結(jié)束
Promise.prototype.then()
promise
對象就可以調(diào)用.then()
运杭,是promise
原型對象上的方法
promise.then(onFulfilled,onRejected);
onFulfilled
參數(shù)對應(yīng)resolve
,處理結(jié)果值函卒,必選
onRejected
參數(shù)對應(yīng)reject辆憔,Error
對象,可選
Promise
對象會在變?yōu)?resolve
或者reject
的時(shí)候分別調(diào)用相應(yīng)注冊的回調(diào)函數(shù)报嵌。
- 當(dāng)
handler
返回一個(gè)正常值的時(shí)候虱咧,這個(gè)值會傳遞給Promise
對象的onFulfilled
方法。- 定義的
handler
中產(chǎn)生異常的時(shí)候锚国,這個(gè)值則會傳遞給Promise
對象的onRejected
方法腕巡。
這兩個(gè)參數(shù)都是兩個(gè)函數(shù)類型,如果這兩個(gè)參數(shù)是非函數(shù)或者被遺漏血筑,就忽略掉這兩個(gè)參數(shù)了绘沉,返回一個(gè)空的
promise
對象。
// 普通的寫法會導(dǎo)致有不穩(wěn)定輸出
function loadScript (src) {
//resolve, reject是可以改變Promise狀態(tài)的豺总,Promise的狀態(tài)是不可逆的
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(src) //fulfilled,result
script.onerror = (err) => reject(err) //rejected,error
document.head.append(script)
})
}
loadScript('./1.js')
.then(loadScript('./2.js'))
.then(loadScript('./3.js'))
//不穩(wěn)定輸出
// 1
// 2
// 3
//----------------------------------------------------------------------------
// 如果把加載2和3的放在1的then方法中
function loadScript (src) {
//resolve, reject是可以改變Promise狀態(tài)的车伞,Promise的狀態(tài)是不可逆的
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(src) //fulfilled,result
script.onerror = (err) => reject(err) //rejected,error
document.head.append(script)
})
}
loadScript('./1.js')
.then(() => {
loadScript('./2.js')
}, (err) => {
console.log(err)
}).then( () => {
loadScript('./3.js')
}, (err) => {
console.log(err)
})
// 穩(wěn)定輸出
// 1
// 不穩(wěn)定輸出
// 2
// 3
// ----------------------------------------------
//但是如果中間有錯(cuò)誤的時(shí)候,下面的3還是會執(zhí)行喻喳。
loadScript('./1.js')
.then(() => {
loadScript('./4.js')
}, (err) => {
console.log(err)
}).then( () => {
loadScript('./3.js')
}, (err) => {
console.log(err)
})
// 1
// 報(bào)錯(cuò)
// 3
// 不符合題意另玖,如果是報(bào)錯(cuò)之后,3不應(yīng)該執(zhí)行
// -------------------------------------------------------
loadScript('./1.js')
.then(() => {
return loadScript('./2.js')
}, (err) => {
console.log(err)
}).then(() => {
return loadScript('./3.js')
}, (err) => {
console.log(err)
})
// 不加返回值,依舊是一個(gè)空的promise對象日矫,無法用resolve, reject影響下一步.then()的執(zhí)行
// 添加返回值之后就可以穩(wěn)定輸出
// 1
// 2
// 3
Promise異常處理
異常處理有以下幾種方法:
then中回調(diào)的onRejected方法
Promise.prototype.catch()(推薦)
catc
h是promise
原型鏈上的方法赂弓,用來捕獲reject
拋出的一場绑榴,進(jìn)行統(tǒng)一的錯(cuò)誤處理哪轿,使用.catch
方法更為常見,因?yàn)楦臃湘準(zhǔn)秸{(diào)用翔怎。
p.catch(onRejected);
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
}).catch(function onRejected(error) {
console.log('onRejected', error)
})
// 相當(dāng)于
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.then(undefined, function onRejected(error) {
console.log('onRejected', error)
})
.catch形式和前面then里面的第二個(gè)參數(shù)的形式窃诉,兩者異常捕獲的區(qū)別:
-
.catch()
是對上一個(gè).then()
返回的promise
進(jìn)行處理,不過第一個(gè)promise
的報(bào)錯(cuò)也順延到了catch
中 - 而
then
的第二個(gè)參數(shù)形式赤套,只能捕獲第一個(gè)promise
的報(bào)錯(cuò)飘痛,如果當(dāng)前then
的resolve
函數(shù)處理中有報(bào)錯(cuò)是捕獲不到的。
所以.catch
是給整個(gè)promise
鏈條注冊的一個(gè)失敗回調(diào)容握。推薦使用P觥!L奘稀塑猖!
function loadScript (src) {
//resolve, reject是可以改變Promise狀態(tài)的,Promise的狀態(tài)是不可逆的
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(src) //fulfilled,result
script.onerror = (err) => reject(err) //rejected,error
document.head.append(script)
})
}
loadScript('./1.js')
.then(() => {
return loadScript('./2.js')
}).then(() => {
return loadScript('./3.js')
})
.catch(err => {
console.log(err)
})
// throw new Error 不要用這個(gè)方法谈跛,要用catch和reject羊苟,去改變promise的狀態(tài)的方式
全局對象上的unhandledrejection事件
還可以在全局對象上注冊一個(gè)unhandledrejection
事件,處理那些代碼中沒有被手動捕獲的promise
異常感憾,當(dāng)然并不推薦使用蜡励。
更合理的是:在代碼中明確捕獲每一個(gè)可能的異常,而不是丟給全局處理
// 瀏覽器
window.addEventListener('unhandledrejection', event => {
const { reason, promise } = event
console.log(reason, promise)
//reason => Promise 失敗原因阻桅,一般是一個(gè)錯(cuò)誤對象
//promise => 出現(xiàn)異常的Promise對象
event.preventDefault()
}, false)
// node
process.on('unhandledRejection', (reason, promise) => {
console.log(reason, promise)
//reason => Promise 失敗原因凉倚,一般是一個(gè)錯(cuò)誤對象
//promise => 出現(xiàn)異常的Promise對象
})
Promise靜態(tài)方法
類型轉(zhuǎn)換 —— Promise.resolve()
靜態(tài)方法 Promise.resolve(value)
可以認(rèn)為是 new Promise()
方法的快捷方式。
Promise.resolve(42)
//等同于
new Promise(function (resolve) {
resolve(42)
})
如果接受的是一個(gè)promise
對象嫂沉,那么這個(gè)對象會原樣返回
const promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true
如果傳入的是一個(gè)對象占遥,且這個(gè)對象也有一個(gè)then
方法,傳入成功和失敗的回調(diào)输瓜,那么在后面執(zhí)行的時(shí)候瓦胎,也是可以按照promise
的then
來拿到。
(這個(gè)then方法尤揣,實(shí)現(xiàn)了一個(gè)thenable的接口搔啊,即可以被then的對象)
使用場景
- 可以是把第三方模擬
promise
庫轉(zhuǎn)化成promise
對象
Promise.reslove({
then: function(onFulfilled, onRejected) {
onFulfilled('foo')
}
})
.then(function (value) {
console.log(value) // foo
})
- 直接將數(shù)值轉(zhuǎn)換成
promise
對象返回
function test (bool) {
if (bool) {
return new Promise((resolve,reject) => {
resolve(30)
})
} else {
return Promise.resolve(42)
}
}
test(1).then((value) => {
console.log(value)
})
Promise.reject()
Promise.reject(error)
是和 Promise.resolve(value)
類似的靜態(tài)方法,是 new Promise()
方法的快捷方式北戏。
創(chuàng)建一個(gè)一定是失敗的promise
對象
Promise.reject(new Error('出錯(cuò)了'))
//等同于
new Promise(function (resolve) {
reject(new Error('出錯(cuò)了'))
})
數(shù)據(jù)聚合 —— Promise.all()
如果需要同時(shí)進(jìn)行多個(gè)異步任務(wù)负芋,使用promise
靜態(tài)方法中的all方法,可以把多個(gè)promise
合并成一個(gè)promise
統(tǒng)一去管理。
Promise.all(promiseArray);
Promise.all
生成并返回一個(gè)新的Promise
對象旧蛾,所以它可以使用Promise
實(shí)例的所有方法莽龟。參數(shù)傳遞promise
數(shù)組中所有的Promise
對象都變?yōu)?code>resolve的時(shí)候,該方法才會返回锨天, 新創(chuàng)建的Promise
則會使用這些promise
的值毯盈。參數(shù)是一個(gè)數(shù)組,元素可以是普通值病袄,也可以是一個(gè)
promise
對象搂赋,輸出順序和執(zhí)行順序有關(guān),該函數(shù)生成并返回一個(gè)新的
Promise
對象益缠,所以它可以使用Promise
實(shí)例的所有方法脑奠。參數(shù)傳遞promise
數(shù)組中所有的Promise
對象都變?yōu)?code>resolve的時(shí)候,該方法才會返回完成幅慌。只要有一個(gè)失敗宋欺,就會走catch
。由于參數(shù)數(shù)組中的每個(gè)元素都是由
Promise.resolve
包裝(wrap
)的胰伍,所以Paomise.all
可以處理不同類型的promose
對象齿诞。
var promise = Promise.all([
// ajax函數(shù)是一個(gè)異步函數(shù)并返回promise,不需要關(guān)心哪個(gè)結(jié)果先回來喇辽,因?yàn)槭嵌纪瓿芍笳喜僮? ajax('/api/users.json'),
ajax('/api/posts.json')
])
Promise.then(function(values) {
console.log(values) //返回的是一個(gè)數(shù)組掌挚,每個(gè)數(shù)組元素對應(yīng)的是其promise的返回結(jié)果
}).catch(function(error) {
console.log(error) // 只要有一個(gè)失敗,那么就會整體失敗走到catch里面
})
競爭 —— Promise.race()
Promise.race(promiseArray);
和
all
一樣會接收一個(gè)數(shù)組菩咨,元素可以是普通值也可以是promise
對象吠式,和all
不同的是,它只會等待第一個(gè)結(jié)束的任務(wù) 抽米。
// 下面的例子如果request超過了500ms特占,那么就會報(bào)超時(shí)錯(cuò)誒,如果小于500ms云茸,則正常返回是目。
const request = ajax('/api/posts.json')
const timeout = new Promise((resovle, reject) => {
setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([
request,
timeout
])
.then(value => {
console.log(value)
})
.catch(error => {
console.log(error)
})
Promise執(zhí)行時(shí)序 —— 宏任務(wù) vs 微任務(wù)
執(zhí)行順序 : 宏任務(wù) => 微任務(wù) => 宏任務(wù)
微任務(wù) 是promise
之后才加入進(jìn)去的,目的是為了提高整體的響應(yīng)能力
我們目前絕大多數(shù)異步調(diào)用都是作為宏任務(wù)執(zhí)行标捺,
promise
的回調(diào) &MutationObserver
&node
中的process.nextTick
會作為微任務(wù)執(zhí)行
下面的例子懊纳,當(dāng)前宏任務(wù)立即執(zhí)行,then
是微任務(wù)會延后執(zhí)行亡容,setTImeout
是異步的一個(gè)宏任務(wù)也會延后執(zhí)行嗤疯。當(dāng)前宏任務(wù)執(zhí)行完畢之后,微任務(wù)會先執(zhí)行完畢之后下一個(gè)宏任務(wù)才會執(zhí)行闺兢。
console.log('global start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve()
.then(( => {
console.log('promise')
}))
.then(( => {
console.log('promise2')
}))
.then(( => {
console.log('promise3')
}))
console.log('global end')
// global start
// global end
// promise
// promise2
// promise3
// setTimeout
具體的牽扯到eventLoop
的東西之后再進(jìn)一步探討茂缚。
深度剖析:手寫一個(gè)Promise源碼
ES6-ES10學(xué)習(xí)版圖
說實(shí)話這個(gè)是最近比較復(fù)雜的一個(gè)筆記了,給自己點(diǎn)個(gè)贊,標(biāo)個(gè)特殊標(biāo)記脚囊。