摘要:一旦狀態(tài)被改變,就不會再變,任何時候都能得到這個結果,與事件回調不同,事件回調在事件過去后無法再調用函數(shù)。如果不設置回調函數(shù),內部拋出的錯誤,不會反應到外部處于時,無法感知的狀態(tài)剛剛開始還是即將完成。以為調用函數(shù),為,和為參數(shù)調用返回。
更好的閱度體驗
前言
API
Promise特點
狀態(tài)跟隨
V8中的async await和Promise
實現(xiàn)一個Promise
參考
前言作為一個前端開發(fā),使用了Promise一年多了,一直以來都停留在API的調用階段,沒有很好的去深入。剛好最近閱讀了V8團隊的一篇如何實現(xiàn)更快的async await,借著這個機會整理了Promise的相關理解。文中如有錯誤,請輕噴~
APIPromise是社區(qū)中對于異步的一種解決方案,相對于回調函數(shù)和事件機制更直觀和容易理解。ES6 將其寫進了語言標準,統(tǒng)一了用法,提供了原生的Promise對象。
這里只對API的一些特點做記錄,如果需要詳細教程,推薦阮老師的Promise對象一文
new Promise
--創(chuàng)建一個promise實例
Promise.prototype.then(resolve, reject)
--then方法返回一個新的Promise實例
Promise.prototype.catch(error)
--.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯誤時的回調函數(shù)。
--錯誤會一直傳遞,直到被catch,如果沒有catch,則沒有任何反應
--catch返回一個新的Promise實例
Promise.prototype.finally()
--指定不管 Promise 對象最后狀態(tài)如何,都會執(zhí)行的操作。
--實現(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.all([promise Array])
--將多個 Promise 實例,包裝成一個新的 Promise 實例
--所有子promise執(zhí)行完成后,才執(zhí)行all的resolve,參數(shù)為所有子promise返回的數(shù)組
--某個子promise出錯時,執(zhí)行all的reject,參數(shù)為第一個被reject的實例的返回值
--某個子promise自己catch時,不會傳遞reject給all,因為catch重新返回一個promise實例
Promise.race([promise Array])
--將多個 Promise 實例,包裝成一個新的 Promise 實例。
--子promise有一個實例率先改變狀態(tài),race的狀態(tài)就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給race的回調函數(shù)。
Promise.resolve()
--將現(xiàn)有對象轉為 Promise 對象
--參數(shù)是promise實例, 原封不動的返回
--參數(shù)是一個thenable對象 將這個對象轉為 Promise 對象,狀態(tài)為resolved
--參數(shù)是一個原始值 返回一個新的 Promise 對象,狀態(tài)為resolved
--不帶有任何參數(shù) 返回一個resolved狀態(tài)的 Promise 對象。
--等價于如下代碼
Promise.resolve("foo") // 等價于 new Promise(resolve => resolve("foo"))
Promise.reject()
--返回一個新的 Promise 實例,該實例的狀態(tài)為rejected
--Promise.reject()方法的參數(shù),會原封不動地作為reject的理由,變成后續(xù)方法的參數(shù)。
很多文章都是把resolve當成fulfilled,本文也是,但本文還有另外一個resolved,指的是該Promise已經被處理,注意兩者的區(qū)別
1. 對象具有三個狀態(tài),分別是pending(進行中)、fulfilled(resolve)(已成功)、reject(已失敗),并且對象的狀態(tài)不受外界改變,只能從pending到fulfilled或者pending到reject。 2. 一旦狀態(tài)被改變,就不會再變,任何時候都能得到這個結果,與事件回調不同,事件回調在事件過去后無法再調用函數(shù)。 3. 一個promise一旦resolved,再次resolve/reject將失效。即只能resolved一次。 4. 值穿透,傳給then或者catch的參數(shù)為非函數(shù)時,會發(fā)生穿透(下面有示例代碼) 5. 無法取消,Promise一旦運行,無法取消。 6. 如果不設置回調函數(shù),Promise內部拋出的錯誤,不會反應到外部 7. 處于pending時,無法感知promise的狀態(tài)(剛剛開始還是即將完成)。值穿透代碼:
new Promise(resolve=>resolve(8)) .then() .then() .then(function foo(value) { console.log(value) // 8 })狀態(tài)追隨
狀態(tài)追隨的概念和下面的v8處理asyac await相關聯(lián)
狀態(tài)跟隨就是指將一個promise(代指A)當成另外一個promise(代指B)的resolve參數(shù),即B的狀態(tài)會追隨A。
如下代碼所示:
const promiseA = new Promise((resolve) => { setTimeout(() => { resolve("ccc") }, 3000) }) const promiseB = new Promise(res => { res(promiseA) }) promiseB.then((arg) => { console.log(arg) // print "ccc" after 3000ms })
按理說,promiseB應該是已經處于resolve的狀態(tài), 但是依然要3000ms后才打印出我們想要的值, 這難免讓人困惑
在ES的標準文檔中有這么一句話可以幫助我們理解:
A resolved promise may be pending, fulfilled or rejected.
就是說一個已經處理的promise,他的狀態(tài)有可能是pending, fulfilled 或者 rejected。 這與我們前面學的不一樣啊, resolved了的promise不應該是處于結果狀態(tài)嗎?這確實有點反直覺,結合上面的例子看,當處于狀態(tài)跟隨時,即使promiseB立即被resolved了,但是因為他追隨了promiseA的狀態(tài),而A的狀態(tài)則是pending,所以才說處于resolved的promiseB的狀態(tài)是pending。
再看另外一個例子:
const promiseA = new Promise((resolve) => { setTimeout(() => { resolve("ccc") }, 3000) }) const promiseB = new Promise(res => { setTimeout(() => { res(promiseA) }, 5000) }) promiseB.then((arg) => { console.log(arg) // print "ccc" after 5000ms })
其實理解了上面的話,這一段的代碼也比較容易理解,只是因為自己之前進了牛角尖,所以特意記錄下來:
3s后 promiseA狀態(tài)變成resolve
5s后 promiseB被resolved, 追隨promiseA的狀態(tài)
因為promiseA的狀態(tài)為resolve, 所以打印 ccc
V8中的async await和Promise在進入正題之前,我們可以先看下面這段代碼:
const p = Promise.resolve(); (async () => { await p; console.log("after:await"); })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
V8團隊的博客中, 說到這段代碼的運行結果有兩種:
Node8(錯誤的):
after: await tick a tick b
Node10(正確的):
tick a tick b after await
ok, 問題來了, 為啥是這個樣子?
先從V8對于await的處理說起, 這里引用一張官方博客的圖來說明Node8 await的偽代碼:
對于上面的例子代碼翻譯過來就(該代碼引用自V8是怎么實現(xiàn)更快的async await)是:
const p = Promise.resolve(); (() => { const implicit_promise = new Promise(resolve => { const promise = new Promise(res => res(p)); promise.then(() => { console.log("after:await"); resolve(); }); }); return implicit_promise; })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
很明顯,內部那一句 new Promise(res => res(p)); 代碼就是一個狀態(tài)跟隨,即promise追隨p的狀態(tài),那這跟上面的結果又有什么關系?
在繼續(xù)深入之前, 我們還需要了解一些概念:
task和microtaskJavaScript 中有 task 和 microtask 的概念。 Task 處理 I/O 和計時器等事件,一次執(zhí)行一個。 Microtask 為 async/await 和 promise 實現(xiàn)延遲執(zhí)行,并在每個任務結束時執(zhí)行。 總是等到 microtasks 隊列被清空,事件循環(huán)執(zhí)行才會返回。
如官方提供的一張圖:
EnquequeJob: 存放兩種類型的任務, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是屬于microtask類型的任務
PromiseReactionJob: 可以通俗的理解為promise中的回調函數(shù)
PromiseResolveThenableJob(promiseToResolve, thenable, then): 創(chuàng)建和 promiseToResolve 關聯(lián)的 resolve function 和 reject function。以 then 為調用函數(shù),thenable 為this,resolve function和reject function 為參數(shù)調用返回。(下面利用代碼講解)
狀態(tài)跟隨的內部再以之前的代碼為例子
const promiseA = new Promise((resolve) => { resolve("ccc") }) const promiseB = new Promise(res => { res(promiseA) })
當promiseB被resolved的時候, 也就是將一個promise(代指A)當成另外一個promise(代指B)的resolve參數(shù),會向EnquequeJob插入一個PromiseResolveThenableJob任務,PromiseResolveThenableJob大概做了如下的事情:
() => { promiseA.then( resolvePromiseB, rejectPromiseB ); }
并且當resolvePromiseB執(zhí)行后, promiseB的狀態(tài)才變成resolve,也就是B追隨A的狀態(tài)
Node10中的流程1. p處于resolve狀態(tài),promise調用then被resolved,同時向microtask插入任務PromiseResolveThenableJob 2. p.then被調用, 向microtask插入任務tickA 3. 執(zhí)行PromiseResolveThenableJob, 向microtask插入任務resolvePromise(如上面的promiseA.then(...)) 4. 執(zhí)行tickA(即輸出tick: a),返回一個promise, 向microtask插入任務tickB 5. 因為microtask的任務還沒執(zhí)行完, 繼續(xù) 6. 執(zhí)行resolvePromise, 此時promise終于變成resolve, 向microtask插入任務"after await" 7. 執(zhí)行tickB(即輸出tick: b) 8. 執(zhí)行"after await"(即輸出"after await")更快的await
老規(guī)矩, 先補一張偽代碼圖:
翻譯過來就是醬紫:
const p = Promise.resolve(); (() => { const implicit_promise = new Promise(resolve => { const promise = Promise.resolve(p) promise.then(() => { console.log("after:await"); resolve(); }); }); return implicit_promise; })(); p.then(() => { console.log("tick:a"); }).then(() => { console.log("tick:b"); });
因為p是一個promise, 然后Promise.resolve會直接將P返回,也就是
p === promise // true
因為直接返回了p,所以省去了中間兩個microtask任務,并且輸出的順序也變得正常,也就是V8所說的更快的async await
實現(xiàn)一個Promise先實現(xiàn)一個基礎的函數(shù)
function Promise(cb) { const that = this that.value = undefined // Promise的值 that.status = "pending" // Promise的狀態(tài) that.resolveArray = [] // resolve函數(shù)集合 that.rejectArray = [] // reject函數(shù)集合 function resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject) } setTimeout(function() { if (that.status === "pending") { // 處于pending狀態(tài) 循環(huán)調用 that.value = value that.status = "resolve" for(let i = 0; i < that.resolveArray.length; i++) { that.resolveArray[i](value) } } }) } function reject(reason) { if (reason instanceof Promise) { return reason.then(resolve, reject) } setTimeout(function() { if (that.status === "pending") { // 處于pending狀態(tài) 循環(huán)調用 that.value = reason that.status = "reject" for(let i = 0; i < that.rejectArray.length; i++) { that.rejectArray[i](reason) } } }) } try { cb(resolve, reject) } catch (e) { reject(e) } } Promise.prototype.then = function(onResolve, onReject) { var that = this var promise2 // 返回的Promise onResolve = typeof onResolve === "function" ? onResolve : function(v) { return v } //如果不是函數(shù) 則處理穿透值 onReject = typeof onReject === "function" ? onReject : function(v) { return v } //如果不是函數(shù) 則處理穿透值 if (that.status === "resolve") { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { try { const x = onResolve(that.value) if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise對象,直接取它的結果做為promise2的結果 x.then(resolve, reject) } else { resolve(x) } } catch (e) { reject(e) } }) }) } if (that.status === "reject") { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { try { const x = onResolve(that.value) if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise對象,直接取它的結果做為promise2的結果 x.then(resolve, reject) } else { reject(x) } } catch (e) { reject(e) } }) }) } if (that.status === "pending") { return promise2 = new Promise(function(resolve, reject) { that.resolveArray.push(function(value) { try { var x = onResolve(value) if (x instanceof Promise) { x.then(resolve, reject) } } catch (e) { reject(e) } }) that.rejectArray.push(function(reason) { try { var x = onReject(reason) if (x instanceof Promise) { x.then(resolve, reject) } } catch (e) { reject(e) } }) }) } } Promise.prototype.catch = function(onReject) { return this.then(null, onReject) }參考
v8是怎么實現(xiàn)更快的 await ?深入理解 await 的運行機制
V8中更快的異步函數(shù)和promise
剖析Promise內部結構,一步一步實現(xiàn)一個完整的、能通過所有Test case的Promise類
PromiseA+
ES6入門
深入Promise
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/108920.html
摘要:的幾個擴展總結描述和相反,當所有的被拒絕之后,方法執(zhí)行完成的決議,如果存在一個執(zhí)行完成的決議,方法則執(zhí)行拒絕里邊的所有實例反過來就好了執(zhí)行到此執(zhí)行到此描述忽略被拒絕的,只需要有一個完成的,方法就執(zhí)行完成操作,如果全部的都被拒絕,方法執(zhí)行拒絕 Promise的幾個擴展API總結 1. Promise.none 描述: 和 Promise.all 相反,當所有的promise被拒絕之后,n...
Promise/async、await幫我們解決了什么 它給我們提供了一種新的異步編程解決方案,同時避免了困擾已久的回調地獄 // 異步的處理可能會產生這樣的回調地獄(第二個異步操作和第一個異步的結果有關系) let Ajax = function(data, success, error){ $.ajax({ data: data, success: function...
Promise/async、await幫我們解決了什么 它給我們提供了一種新的異步編程解決方案,同時避免了困擾已久的回調地獄 // 異步的處理可能會產生這樣的回調地獄(第二個異步操作和第一個異步的結果有關系) let Ajax = function(data, success, error){ $.ajax({ data: data, success: function...
Promise/async、await幫我們解決了什么 它給我們提供了一種新的異步編程解決方案,同時避免了困擾已久的回調地獄 // 異步的處理可能會產生這樣的回調地獄(第二個異步操作和第一個異步的結果有關系) let Ajax = function(data, success, error){ $.ajax({ data: data, success: function...
摘要:眾所周知和都屬于上述異步任務的一種那到底為什么和會有順序之分這就是我想分析總結的問題所在了和的作用是為了讓瀏覽器能夠從內部獲取的內容并確保執(zhí)行棧能夠順序進行。只要執(zhí)行棧沒有其他在執(zhí)行,在每個結束時,隊列就會在回調后處理。 前言 我是在做前端面試題中看到了setTimeout和Promise的比較,然后第一次看到了microtask和macrotask的概念,在閱讀了一些文章之后發(fā)現(xiàn)沒有...
閱讀 1875·2021-11-24 10:21
閱讀 1304·2021-09-22 15:25
閱讀 3224·2019-08-30 15:55
閱讀 766·2019-08-30 15:54
閱讀 3527·2019-08-30 14:20
閱讀 1713·2019-08-30 14:06
閱讀 692·2019-08-30 13:11
閱讀 3244·2019-08-29 16:43