ES6 promise 用法小結(jié)
Js
是一?單線程語言纷闺,早期解決異步問題佃乘,大部分是通過回調(diào)函數(shù)進行犬金。
比如我們發(fā)送 ajax 請求念恍,就是常見的一個異步場景,發(fā)送請求后晚顷,一段時間服務(wù)器給我們響應(yīng)峰伙,然后才拿到結(jié)果。如果我們希望在異步結(jié)束之后執(zhí)行某個操作该默,就只能通過回調(diào)函數(shù)的方式進行操作
const request = function (callback) {
setTimeout(function () {
callback()
}, 1000)
}
request(function () {
console.log(123)
})
// 以上代碼執(zhí)行結(jié)果:1s 后輸出 123
// request 就是一個異步函數(shù)瞳氓,里面執(zhí)行的 setTimeout 會在 1s 之后調(diào)用傳入的 callback 函數(shù),
// 如果后續(xù)還有內(nèi)容需要在異步函數(shù)結(jié)束時輸出,就需要多個異步函數(shù)進行嵌套栓袖,非常不利于后續(xù)的維護顿膨。
setTimeout(function () {
console.log(123)
setTimeout(function () {
console.log(321)
// ...
}, 2000)
}, 1000)
為了使回調(diào)函數(shù)以更優(yōu)雅的方式進行調(diào)用锅锨,在 ES6 中引入了 promise
,讓異步 操作的變得「同步化」恋沃。
1,Promise 基礎(chǔ)
通過 new Promise()
即可構(gòu)造一個 promise 實例必指,這個構(gòu)造函數(shù)接受一個函數(shù)囊咏,接受兩個參數(shù):resolve
和 reject
,代表改變實例的狀態(tài)到 已完成
或是 已拒絕
const promise = new Promise(function (resolve, reject) {
console.log('promise called')
setTimeout(function () {
resolve()
}, 3000)
})
promise.then(function () {
console.log('promise resolve callback')
})
// 先打印出 promise called塔橡, 3s 后打印 promise resolve callback
function promise1() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1s后輸出')
resolve()
}, 1000)
})
}
function promise2() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('2s后輸出')
resolve()
}, 2000)
})
}
// 以下兩個promise實例梅割,串聯(lián)起來即可寫為:
promise1().then(function () {
return promise2()
})
或
promise1().then(promise2)
控制臺打印結(jié)果:1s之后出現(xiàn) 1s后輸出
,再經(jīng)過2s出現(xiàn)2s后輸出
葛家。實例中户辞,當(dāng)前promise如果狀態(tài)變?yōu)橐淹瓿?執(zhí)行resolve方法),就會去執(zhí)行 then
方法中的下一個 promise
函數(shù)癞谒。同樣的如果promise變成已拒絕狀態(tài)(執(zhí)行reject方法)底燎,就會進入后續(xù)的異常處理函數(shù)中。
function promise3() {
return new Promise(function (resolve, reject) {
var random = Math.random() * 10 // 隨機一個 1 - 10的數(shù)字
setTimeout(function () {
if (random >= 5) {
resolve(random) // 把隨機生成的數(shù)字傳給了 resolve, 在 then 中可以拿到這個值
} else {
reject(random) // 把隨機生成的數(shù)字傳給了 reject弹砚,在 then 中可以拿到這個值
}
}, 1000)
})
}
var onResolve = function (val) {
console.log('已完成:輸出的數(shù)字是:', val)
}
var onReject = function (val) {
console.log('已拒絕:輸出的數(shù)字是:', val)
}
// promise 的then也可以接受兩個參數(shù)双仍,第一個參數(shù)是 resolve 后執(zhí)行的,第二個參數(shù)是 reject 后執(zhí)行的
promise3().then(onResolve, onReject)
// 也可以通過 .catch 方法攔截狀態(tài)變?yōu)橐丫芙^時的 promise
promise3().catch(onReject).then(onResolve)
// 也可以通過 try catch 進行攔截狀態(tài)變?yōu)橐丫芙^的 promise
try {
promise3().then(onResolve)
} catch (e) {
onReject(e)
}
以上使用3種方式攔截最終變?yōu)椤敢丫芙^」?fàn)顟B(tài)的 promise桌吃,分別是使用 then 的第二個參數(shù)
朱沃,使用 .catch
方法捕獲前方 promise 拋出的異常,使用 try catch
攔截代碼塊中 promise 拋出的異常
我們可以發(fā)現(xiàn)茅诱,在改變 promise
狀態(tài)時調(diào)用 resolve
和 reject
函數(shù)的時候逗物,可以給下一步 then
中執(zhí)行的函數(shù)傳遞參數(shù)。
2瑟俭,封裝異步操作為promise
我們可以將任何接受回調(diào)的函數(shù)封裝為一個promise
, 實例:
// 原函數(shù)
function func(callback) {
setTimeout(function () {
console.log('1s 后顯示')
callback()
}, 1000)
}
var callback = function () {
console.log('在異步結(jié)束后打印')
}
// 用傳入回調(diào)函數(shù)的方式執(zhí)行
func(callback)
以上實例是最傳統(tǒng)的使用傳入回調(diào)函數(shù)的方式在異步結(jié)束后執(zhí)行函數(shù)翎卓。我們可以通過封裝
promise
的方式,將這個異步函數(shù)變?yōu)?promise
:
function func() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1s 后顯示')
resolve()
})
})
}
var callback = function () {
console.log('在異步結(jié)束后打印')
}
func().then(function () {
callback()
})
再比如尔当,我們發(fā)送 ajax
請求也可以封裝為 promise
:
function ajax(url, success, fail) {
var client = new XMLHttpRequest();
client.open('GET', url)
client.onreadystatechange = function () {
if (this.readyState !== 4) {
// this.readyState擴展:
// 0: 未初始化莲祸,還沒調(diào)用 send() 方法
// 1: 載入,已調(diào)用send()方法椭迎,正在發(fā)送請求
// 2: 載入完成锐帜,send()執(zhí)行完畢,已接受全部響應(yīng)內(nèi)容
// 3: 交互畜号,正在解析響應(yīng)內(nèi)容
// 4: 完成缴阎,響應(yīng)內(nèi)容解析完成,可以直接使用responseText數(shù)據(jù)
return
}
if (this.status === 200) {
success(this.response)
} else {
fail(new Error(this.statusText))
}
}
client.send()
}
ajax('http://localhost:8080/home/swiper', function (res) {
console.log('成功')
console.log(res)
}, function (err) {
console.log('失敗', err)
})
以上 ajax 請求简软,通過封裝
promise
的方式蛮拔,在原來的執(zhí)行回調(diào)函數(shù)的地方述暂,更改當(dāng)前 promise
的狀態(tài),就可以通過鏈?zhǔn)秸{(diào)用:
function ajax(url) {
return new Promise(function (resolve, reject) {
var ct = new XMLHttpRequest();
ct.open('GET', url)
ct.onreadystatechange = function () {
if (this.readyState !== 4) {
return
}
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
ct.send()
})
}
ajax('http://localhost:8080/home/swiper').catch(function () {
console.log('失敗')
}).then(function (res) {
console.log('成功')
console.log(res)
})
我們可以把任何一個函數(shù)或者是異步函數(shù)改為promise
建炫,尤其是異步函數(shù)畦韭,改為 promise
中后即可進行鏈?zhǔn)秸{(diào)用,增強可讀性
3肛跌,小總結(jié)
1艺配,promise 有三種狀態(tài),
進行中(Pending)
衍慎、已完成(Fulfilled)
转唉、已拒絕(Rejected)
,進行中狀態(tài)可以更改為已完成 或 已拒絕稳捆,已經(jīng)更改過狀態(tài)后無法繼續(xù)更改(例如從已完成改為已拒絕)赠法。2,ES6 中的 Promise 構(gòu)造函數(shù)乔夯,我們構(gòu)造之后需要傳入一個函數(shù)砖织,他接受兩個函數(shù)參數(shù),執(zhí)行第一個參數(shù)之后就會改變當(dāng)前 promise 為
已完成
狀態(tài)驯嘱,執(zhí)行第二個參數(shù)之后就會變?yōu)?已拒絕
狀態(tài)镶苞。3,必須有一個
then
方法用以訪問其當(dāng)前值和原因鞠评。promise的then
方法接受兩個參數(shù):promise.then(onFulfilled, onRejected)
他們都是可選參數(shù)茂蚓,他們都是函數(shù)。如果onFulfilled
或onRejected
不是函數(shù)剃幌,則需要忽略他們4聋涨,已拒絕的 promise,后續(xù)可以通過
.catch
方法或是.then
方法的第二個參數(shù)或是try catch
進行捕獲负乡。-
5牍白,
then
方法可以被同一個promise調(diào)用多次。- 當(dāng) promise 成功執(zhí)行的時候抖棘,所有的 onFulfilled 需按照其注冊順序依次回調(diào)
- 當(dāng) promise 被拒絕執(zhí)行的時候茂腥,所有的 onRejected 需按照其注冊順序依次回調(diào)
then 方法必須返回一個 promise 對象: promise2 = promise1.then(onFulfilled, onRejected) - 只要
onFulfilled
或者onRejected
返回一個值x
,promise2 都會進入 onFulfilled 狀態(tài)
- 如果
onFulfilled
或者onRejected
拋出一個異常e
切省,則 promise2 必須拒絕執(zhí)行最岗,并返回拒因 e - 如果 onFulfilled 不是函數(shù)且 promise1 狀態(tài)變?yōu)橐淹瓿桑?promise2 必須成功執(zhí)行并返回相同的值
- 如果 onRejected 不是函數(shù)且 promise1 狀態(tài)變?yōu)橐丫芙^, promise2 必須執(zhí)行拒絕回調(diào)并返回相同的據(jù)因
var promise1 = new Promise((resolve, reject) => {
reject()
})
promise1
.then(null, function () {
return 123
})
.then(null, null)
.then(null, null)
.then(
() => {
console.log('promise2 已完成')
},
() => {
console.log('promise2 已拒絕')
})
以上代碼輸出:promise2 已完成
以上代碼可改寫為:
var promise1 = new Promise(function (resolve, reject) { reject() })
var promise2 = promise1.then(null, function () { return 123 })
var promise3 = promise2.then(null, null) // 如果 onFulfilled 不是函數(shù)且 promise2 狀態(tài)變?yōu)橐淹瓿桑?promise3 必須成功執(zhí)行并返回和 promise2 相同的值, 即 123
var promise4 = promise3.then(null, null) // 同理朝捆,promise4 也能拿到 123 的值
promise4
.then(val => {
console.log('promise2 已完成', val) // promise2 已完成 123
}, () => {
console.log('promise2 已拒絕')
})
實例:
var promise1 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1)
resolve()
}, 1000)
})
}
var promise2 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2)
resolve()
}, 2000)
})
}
promise1()
.then(function () {
return promise2() // 此處返回一個 promise 實例
})
.then(function () {
console.log('已完成')
}, function () {
console.log('已拒絕')
})
4, promise 構(gòu)造函數(shù)上的 靜態(tài)方法
- 4.1, promise.resolve
返回一個 promise 實例般渡,并將它的狀態(tài)設(shè)置為已完成,同時將他的結(jié)果作為傳入 promise 實例的值
var promise = Promise.resolve(123)
promise.then(function (val) {
console.log('已完成', val)
})
Promise.resolve 的參數(shù)也可以處理對象、函數(shù)等內(nèi)容
- 4.2驯用,promise.reject
返回一個 promise 實例脸秽,并將它的狀態(tài)設(shè)置為已拒絕,同時也將他的結(jié)果作為原因傳入 onRejected 函數(shù)
var promise = Promise.reject(123)
promise.then(null, function (val) {
console.log('已拒絕', val)
})
- 4.3蝴乔,Promise.all
返回一個 promise 實例记餐,接受一個數(shù)組,里面含有多個 promise 實例薇正,當(dāng)所有 promise 實例都成 已完成 狀態(tài)時剥扣,進入已完成狀態(tài),否則進入已拒絕狀態(tài)铝穷。
var promise1 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1)
resolve()
}, 1000)
})
}
var promise2 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2)
resolve()
}, 1000)
})
}
Promise.all([promise1(), promise2()]).then(function () {
console.log('全部 promise 均已完成')
})
以上代碼為多個 promise 同時進行,等待 1s 打印 1 之后佳魔,再等待 1s 就 會打印 2 和全部 promise 均已完成曙聂。
- 4.4,Promise.race
返回一個 promise 實例鞠鲜,接受一個數(shù)組宁脊,里面含有多個 promise 實例,當(dāng)有一個 promise 實例狀態(tài)改變時贤姆,就進入該狀態(tài)且不可改變榆苞。這里所有的 promise 實例為競爭關(guān)系,只選擇第一個進入改變狀態(tài)的 promise 的值霞捡。
var promise1 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1)
resolve(1)
}, 1000)
})
}
var promise2 = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2)
resolve(2)
}, 1000)
})
}
Promise.race([promise1(), promise2()]).then(function (val) {
console.log('有一個 promise 狀態(tài)已經(jīng)改變', val)
})
5, generator / async await
ES6 之后坐漏,我們可以使用 generator 和 async/await 來操作 promise,比起使用 promise 串行的調(diào)用來說碧信,從語法層面 讓調(diào)用關(guān)系 顯得更加串行赊琳。
function promise1() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1)
resolve()
}, 1000)
})
}
function promise2() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2)
resolve()
}, 1000)
})
}
// 使用 generator 函數(shù)
function* gen() {
yield promise1()
yield promise2()
}
var g = gen()
g.next()
g.next() // 1 2
// 使用 async/await 函數(shù)
(async function () {
try {
await promise1()
await promise2()
console.log('已完成')
} catch (e) {
console.log(e)
console.log('已拒絕')
}
}()) // 1 2 已完成