- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- JavaScript中Promise如何使用
JavaScript中Promise如何使用,針對這個(gè)問(wèn)題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
1.什么是 Promise
promise 是目前 JS 異步編程的主流解決方案,遵循 Promises/A+ 方案。
2.Promise 原理簡(jiǎn)析
(1)promise 本身相當于一個(gè)狀態(tài)機,擁有三種狀態(tài):
pending
fulfilled
rejected
一個(gè) promise 對象初始化時(shí)的狀態(tài)是 pending,調用了 resolve 后會(huì )將 promise 的狀態(tài)扭轉為 fulfilled,調用 reject 后會(huì )將 promise 的狀態(tài)扭轉為 rejected,這兩種扭轉一旦發(fā)生便不能再扭轉該 promise 到其他狀態(tài)。
(2)promise 對象原型上有一個(gè) then 方法,then 方法會(huì )返回一個(gè)新的 promise 對象,并且將回調函數 return 的結果作為該 promise resolve 的結果,then 方法會(huì )在一個(gè) promise 狀態(tài)被扭轉為 fulfilled 或 rejected 時(shí)被調用。then 方法的參數為兩個(gè)函數,分別為 promise 對象的狀態(tài)被扭轉為 fulfilled 和 rejected 對應的回調函數。
3.Promise 如何使用
構造一個(gè) promise 對象,并將要執行的異步函數傳入到 promise 的參數中執行,并且在異步執行結束后調用 resolve( ) 函數,就可以在 promise 的 then 方法中獲取到異步函數的執行結果:
new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }).then( res => {}, err => {} )
同時(shí)在 Promise 還為我們實(shí)現了很多方便使用的方法:
Promise.resolve
Promise.resolve 返回一個(gè) fulfilled 狀態(tài)的 promise。
const a = Promise.resolve(1) a.then( res => { // res = 1 }, err => {} )
Promise.all
Promise.all 接收一個(gè) promise 對象數組作為參數,只有全部的 promise 都已經(jīng)變?yōu)?fulfilled 狀態(tài)后才會(huì )繼續后面的處理。Promise.all 本身返回的也是一個(gè) promise 。
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise1') }, 100) }) const promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise2') }, 100) }) const promises = [promise1, promise2] Promise.all(promises).then( res => { // promises 全部變?yōu)?nbsp;fulfilled 狀態(tài)的處理 }, err => { // promises 中有一個(gè)變?yōu)?nbsp;rejected 狀態(tài)的處理 } )
Promise.race
Promise.race 和 Promise.all 類(lèi)似,只不過(guò)這個(gè)函數會(huì )在 promises 中第一個(gè) promise 的狀態(tài)扭轉后就開(kāi)始后面的處理(fulfilled、rejected 均可) 。
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise1') }, 100) }) const promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('promise2') }, 1000) }) const promises = [promise1, promise2] Promise.race(promises).then( res => { // 此時(shí)只有 promise1 resolve 了,promise2 仍處于 pending 狀態(tài) }, err => {} )
配合 async await 使用
現在的開(kāi)發(fā)場(chǎng)景中我們大多會(huì )用 async await 語(yǔ)法糖來(lái)等待一個(gè) promise 的執行結果,使代碼的可讀性更高。async 本身是一個(gè)語(yǔ)法糖,將函數的返回值包在一個(gè) promise 中返回。
// async 函數會(huì )返回一個(gè) promise const p = async function f() { return 'hello world' } p.then(res => console.log(res)) // hello world
開(kāi)發(fā)技巧
在前端開(kāi)發(fā)上 promise 大多被用來(lái)請求接口,Axios 庫也是開(kāi)發(fā)中使用最頻繁的庫,但是頻繁的 try catch 撲捉錯誤會(huì )讓代碼嵌套很?chē)乐???紤]如下代碼的優(yōu)化方式。
const getUserInfo = async function() { return new Promise((resolve, reject) => { // resolve() || reject() }) } // 為了處理可能的拋錯,不得不將 try catch 套在代碼外邊,一旦嵌套變多,代碼可讀性就會(huì )急劇下降 try { const user = await getUserInfo() } catch (e) {}
好的處理方法是在異步函數中就將錯誤 catch,然后正常返回,如下所示 ?
const getUserInfo = async function() { return new Promise((resolve, reject) => { // resolve() || reject() }).then( res => { return [res, null] // 處理成功的返回結果 }, err => { return [null, err] // 處理失敗的返回結果 } ) } const [user, err] = await getUserInfo() if (err) { // err 處理 } // 這樣的處理是不是清晰了很多呢
4.Promise 源碼實(shí)現
知識的學(xué)習需要知其然且知其所以然,所以通過(guò)一點(diǎn)點(diǎn)實(shí)現的一個(gè) promise 能夠對 promise 有著(zhù)更深刻的理解。
(1)首先按照最基本的 promise 調用方式實(shí)現一個(gè)簡(jiǎn)單的 promise (基于 ES6 規范編寫(xiě)),假設我們有如下調用方式:
new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) .then( res => { console.log(res) return 2 }, err => {} ) .then( res => { console.log(res) }, err => {} )
我們首先要實(shí)現一個(gè) Promise 的類(lèi),這個(gè)類(lèi)的構造函數會(huì )傳入一個(gè)函數作為參數,并且向該函數傳入 resolve 和 reject 兩個(gè)方法。
初始化 Promise 的狀態(tài)為 pending。
class MyPromise { constructor(executor) { this.executor = executor this.value = null this.status = 'pending' const resolve = value => { if (this.status === 'pending') { this.value = value // 調用 resolve 后記錄 resolve 的值 this.status = 'fulfilled' // 調用 resolve 扭轉 promise 狀態(tài) } } const reject = value => { if (this.status === 'pending') { this.value = value // 調用 reject 后記錄 reject 的值 this.status = 'rejected' // 調用 reject 扭轉 promise 狀態(tài) } } this.executor(resolve, reject) }
(2)接下來(lái)要實(shí)現 promise 對象上的 then 方法,then 方法會(huì )傳入兩個(gè)函數作為參數,分別作為 promise 對象 resolve 和 reject 的處理函數。
這里要注意三點(diǎn):
then 函數需要返回一個(gè)新的 promise 對象
執行 then 函數的時(shí)候這個(gè) promise 的狀態(tài)可能還沒(méi)有被扭轉為 fulfilled 或 rejected
一個(gè) promise 對象可以同時(shí)多次調用 then 函數
class MyPromise { constructor(executor) { this.executor = executor this.value = null this.status = 'pending' this.onFulfilledFunctions = [] // 存放這個(gè) promise 注冊的 then 函數中傳的第一個(gè)函數參數 this.onRejectedFunctions = [] // 存放這個(gè) promise 注冊的 then 函數中傳的第二個(gè)函數參數 const resolve = value => { if (this.status === 'pending') { this.value = value this.status = 'fulfilled' this.onFulfilledFunctions.forEach(onFulfilled => { onFulfilled() // 將 onFulfilledFunctions 中的函數拿出來(lái)執行 }) } } const reject = value => { if (this.status === 'pending') { this.value = value this.status = 'rejected' this.onRejectedFunctions.forEach(onRejected => { onRejected() // 將 onRejectedFunctions 中的函數拿出來(lái)執行 }) } } this.executor(resolve, reject) } then(onFulfilled, onRejected) { const self = this if (this.status === 'pending') { /** * 當 promise 的狀態(tài)仍然處于 ‘pending’ 狀態(tài)時(shí),需要將注冊 onFulfilled、onRejected 方法放到 promise 的 onFulfilledFunctions、onRejectedFunctions 備用 */ return new MyPromise((resolve, reject) => { this.onFulfilledFunctions.push(() => { const thenReturn = onFulfilled(self.value) resolve(thenReturn) }) this.onRejectedFunctions.push(() => { const thenReturn = onRejected(self.value) resolve(thenReturn) }) }) } else if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { const thenReturn = onFulfilled(self.value) resolve(thenReturn) }) } else { return new MyPromise((resolve, reject) => { const thenReturn = onRejected(self.value) resolve(thenReturn) }) } } }
對于以上完成的 MyPromise 進(jìn)行測試,測試代碼如下:
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) p.then(res => { console.log('first then', res) return res + 1 }).then(res => { console.log('first then', res) }) p.then(res => { console.log(`second then`, res) return res + 1 }).then(res => { console.log(`second then`, res) }) /** * 輸出結果如下: * first then 1 * first then 2 * second then 1 * second then 2 */
(3)在 promise 相關(guān)的內容中,有一點(diǎn)常常被我們忽略,當 then 函數中返回的是一個(gè) promise 應該如何處理?
考慮如下代碼:
// 使用正確的 Promise new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) .then(res => { console.log('外部 promise') return new Promise((resolve, reject) => { resolve(`內部 promise`) }) }) .then(res => { console.log(res) }) /** * 輸出結果如下: * 外部 promise * 內部 promise */
通過(guò)以上的輸出結果不難判斷,當 then 函數返回的是一個(gè) promise 時(shí),promise 并不會(huì )直接將這個(gè) promise 傳遞到下一個(gè) then 函數,而是會(huì )等待該 promise resolve 后,將其 resolve 的值,傳遞給下一個(gè) then 函數,找到我們實(shí)現的代碼的 then 函數部分,做以下修改:
then(onFulfilled, onRejected) { const self = this if (this.status === 'pending') { return new MyPromise((resolve, reject) => { this.onFulfilledFunctions.push(() => { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { // 當返回值為 promise 時(shí),等該內部的 promise 狀態(tài)扭轉時(shí),同步扭轉外部的 promise 狀態(tài) thenReturn.then(resolve, reject) } else { resolve(thenReturn) } }) this.onRejectedFunctions.push(() => { const thenReturn = onRejected(self.value) if (thenReturn instanceof MyPromise) { // 當返回值為 promise 時(shí),等該內部的 promise 狀態(tài)扭轉時(shí),同步扭轉外部的 promise 狀態(tài) thenReturn.then(resolve, reject) } else { resolve(thenReturn) } }) }) } else if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { // 當返回值為 promise 時(shí),等該內部的 promise 狀態(tài)扭轉時(shí),同步扭轉外部的 promise 狀態(tài) thenReturn.then(resolve, reject) } else { resolve(thenReturn) } }) } else { return new MyPromise((resolve, reject) => { const thenReturn = onRejected(self.value) if (thenReturn instanceof MyPromise) { // 當返回值為 promise 時(shí),等該內部的 promise 狀態(tài)扭轉時(shí),同步扭轉外部的 promise 狀態(tài) thenReturn.then(resolve, reject) } else { resolve(thenReturn) } }) } }
(4) 之前的 promise 實(shí)現代碼仍然缺少很多細節邏輯,下面會(huì )提供一個(gè)相對完整的版本,注釋部分是增加的代碼,并提供了解釋。
class MyPromise { constructor(executor) { this.executor = executor this.value = null this.status = 'pending' this.onFulfilledFunctions = [] this.onRejectedFunctions = [] const resolve = value => { if (this.status === 'pending') { this.value = value this.status = 'fulfilled' this.onFulfilledFunctions.forEach(onFulfilled => { onFulfilled() }) } } const reject = value => { if (this.status === 'pending') { this.value = value this.status = 'rejected' this.onRejectedFunctions.forEach(onRejected => { onRejected() }) } } this.executor(resolve, reject) } then(onFulfilled, onRejected) { const self = this if (typeof onFulfilled !== 'function') { // 兼容 onFulfilled 未傳函數的情況 onFulfilled = function() {} } if (typeof onRejected !== 'function') { // 兼容 onRejected 未傳函數的情況 onRejected = function() {} } if (this.status === 'pending') { return new MyPromise((resolve, reject) => { this.onFulfilledFunctions.push(() => { try { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { // catch 執行過(guò)程的錯誤 reject(err) } }) this.onRejectedFunctions.push(() => { try { const thenReturn = onRejected(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { // catch 執行過(guò)程的錯誤 reject(err) } }) }) } else if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { try { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { // catch 執行過(guò)程的錯誤 reject(err) } }) } else { return new MyPromise((resolve, reject) => { try { const thenReturn = onRejected(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { // catch 執行過(guò)程的錯誤 reject(err) } }) } } }
(5)至此一個(gè)相對完整的 promise 已經(jīng)實(shí)現,但他仍有一些問(wèn)題,了解宏任務(wù)、微任務(wù)的同學(xué)一定知道,promise 的 then 函數實(shí)際上是注冊一個(gè)微任務(wù),then 函數中的參數函數并不會(huì )同步執行。
查看如下代碼:
new Promise((resolve,reject)=>{ console.log(`promise 內部`) resolve() }).then((res)=>{ console.log(`第一個(gè) then`) }) console.log(`promise 外部`) /** * 輸出結果如下: * promise 內部 * promise 外部 * 第一個(gè) then */ // 但是如果使用我們寫(xiě)的 MyPromise 來(lái)執行上面的程序 new MyPromise((resolve,reject)=>{ console.log(`promise 內部`) resolve() }).then((res)=>{ console.log(`第一個(gè) then`) }) console.log(`promise 外部`) /** * 輸出結果如下: * promise 內部 * 第一個(gè) then * promise 外部 */
以上的原因是因為的我們的 then 中的 onFulfilled、onRejected 是同步執行的,當執行到 then 函數時(shí)上一個(gè) promise 的狀態(tài)已經(jīng)扭轉為 fulfilled 的話(huà)就會(huì )立即執行 onFulfilled、onRejected。
要解決這個(gè)問(wèn)題也非常簡(jiǎn)單,將 onFulfilled、onRejected 的執行放在下一個(gè)事件循環(huán)中就可以了。
if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { setTimeout(() => { try { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { // catch 執行過(guò)程的錯誤 reject(err) } }) }, 0) }
關(guān)于宏任務(wù)和微任務(wù)的解釋?zhuān)以诰蚪鹕峡吹竭^(guò)一篇非常棒的文章,它用銀行柜臺的例子解釋了為什么會(huì )同時(shí)存在宏任務(wù)和微任務(wù)兩個(gè)隊列,文章鏈接貼到文末感興趣的可以看一下。
5.Promise/A+ 方案解讀
我們上面實(shí)現的一切邏輯,均是按照 Promise/A+ 規范實(shí)現的,Promise/A+ 規范說(shuō)的大部分內容已經(jīng)在上面 promise 的實(shí)現過(guò)程中一一講解。接下來(lái)講述相當于一個(gè)匯總:
1. promise 有三個(gè)狀態(tài) pending、fulfilled、rejected,只能由 pending 向 fulfilled 、rejected 兩種狀態(tài)發(fā)生改變。
2. promise 需要提供一個(gè) then 方法,then 方法接收 (onFulfilled,onRejected) 兩個(gè)函數作為參數。
3. onFulfilled、onRejected 須在 promise 完成后后(狀態(tài)扭轉)后調用,且只能調用一次。
4. onFulfilled、onRejected 僅僅作為函數進(jìn)行調用,不能夠將 this 指向調用它的 promise。
5. onFulfilled、onRejected 必須在執行上下文棧只包含平臺代碼后才能執行。平臺代碼指 引擎,環(huán)境,Promise 實(shí)現代碼。(PS:這處規范要求 onFulfilled、onRejected 函數的執行必須在 then 被調用的那個(gè)事件循環(huán)之后的事件循環(huán)。但是規范并沒(méi)有要求是把它們作為一個(gè)微任務(wù)或是宏任務(wù)去執行,只是各平臺的實(shí)現均把 Promise 的 onFulfilled、onRejected 放到微任務(wù)隊列中去執行了)。
6. onFulfilled、onRejected 必須是個(gè)函數,否則忽略。
7. then 方法可以被一個(gè) promise 多次調用。
8. then 方法需要返回一個(gè) promise。
9. Promise 的解析過(guò)程是一個(gè)抽象操作,將 Promise 和一個(gè)值作為輸入,我們將其表示為 [[Resolve]](promise,x), [[Resolve]](promise,x) 是創(chuàng )建一個(gè) Resolve 方法并傳入 promise,x(promise 成功時(shí)返回的值) 兩個(gè)參數,如果 x 是一個(gè) thenable 對象(含有 then 方法),并且假設 x 的行為類(lèi)似 promise, [[Resolve]](promise,x) 會(huì )創(chuàng )造一個(gè)采用 x 狀態(tài)的 promise,否則 [[Resolve]](promise,x) 會(huì )用 x 來(lái)扭轉 promise 的狀態(tài)。取得輸入的不同的 promise 實(shí)現方式可以進(jìn)行交互,只要它們都暴露了 Promise/A+ 兼容方法即可。它也允許 promise 使用合理的 then 方法同化一些不合規范的 promise 實(shí)現。
第 9 點(diǎn)只看文檔比較晦澀難懂,其實(shí)它是針對我們的 then 方法中的這行代碼做的規范解釋。
return new MyPromise((resolve, reject) => { try { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { // ? 就是這一行代碼 thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { reject(err) } })
因為 Promise 并不是 JS 一開(kāi)始就有的標準,是被很多第三方獨立實(shí)現的一個(gè)方法,所以無(wú)法通過(guò) instanceof 來(lái)判斷返回值是否是一個(gè) promise 對象,所以為了使不同的 promise 可以交互,才有了我上面提到的第 9 條規范。當返回值 thenReturn 是一個(gè) promise 對象時(shí),我們需要等待這個(gè) promise 的狀態(tài)發(fā)生扭轉并用它的返回值來(lái) resolve 外層的 promise。
所以最后我們還需要實(shí)現 [[Resolve]](promise,x),來(lái)滿(mǎn)足 promise 規范,規范如下所示。
/** * resolvePromise 函數即為根據 x 的值來(lái)決定 promise2 的狀態(tài)的函數 * @param {Promise} promise2 then 函數需要返回的 promise 對象 * @param {any} x onResolve || onReject 執行后得到的返回值 * @param {Function} resolve MyPromise 中的 resolve 方法 * @param {Function} reject MyPromise 中的 reject 方法 */ function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { // 2.3.1 promise2 和 x 指向同一個(gè)對象 reject(new TypeError()) return } if (x instanceof MyPromise) { // 2.3.2 x 是一個(gè) MyPromise 的實(shí)例,采用他的狀態(tài) if (x.status === 'pending') { x.then( value => { resolvePromise(promise2, value, resolve, reject) }, err => { reject(err) } ) } else { x.then(resolve, reject) } return } if (x && (typeof x === 'function' || typeof x === 'object')) { // 2.3.3 x 是一個(gè)對象或函數 try { const then = x.then // 2.3.3.1 聲明 變量 then = x.then let promiseStatusConfirmed = false // promise 的狀態(tài)確定 if (typeof then === 'function') { // 2.3.3.3 then 是一個(gè)方法,把 x 綁定到 then 函數中的 this 上并調用 then.call( x, value => { // 2.3.3.3.1 then 函數返回了值 value,則使用 [[Resolve]](promise, value),用于監測 value 是不是也是一個(gè) thenable 的對象 if (promiseStatusConfirmed) return // 2.3.3.3.3 即這三處誰(shuí)選執行就以誰(shuí)的結果為準 promiseStatusConfirmed = true resolvePromise(promise2, value, resolve, reject) return }, err => { // 2.3.3.3.2 then 函數拋錯 err ,用 err reject 當前的 promise if (promiseStatusConfirmed) return // 2.3.3.3.3 即這三處誰(shuí)選執行就以誰(shuí)的結果為準 promiseStatusConfirmed = true reject(err) return } ) } else { // 2.3.3.4 then 不是一個(gè)方法,則用 x 扭轉 promise 狀態(tài) 為 fulfilled resolve(x) } } catch (e) { // 2.3.3.2 在取得 x.then 的結果時(shí)拋出錯誤 e 的話(huà),使用 e reject 當前的 promise if (promiseStatusConfirmed) return // 2.3.3.3.3 即這三處誰(shuí)選執行就以誰(shuí)的結果為準 promiseStatusConfirmed = true reject(e) return } } else { resolve(x) // 2.3.4 如果 x 不是 object || function,用 x 扭轉 promise 狀態(tài) 為 fulfilled } }
然后我們就可以用 resolcePromise 方法替換之前的這部分代碼。
return new MyPromise((resolve, reject) => { try { const thenReturn = onFulfilled(self.value) if (thenReturn instanceof MyPromise) { thenReturn.then(resolve, reject) } else { resolve(thenReturn) } } catch (err) { reject(err) } }) // 變成下面這樣 ? return new MyPromise((resolve, reject) => { try { const thenReturn = onFulfilled(self.value) resolvePromise(resolve,reject) } catch (err) { reject(err) } })
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站