日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

promise链式调用_这一次,彻底弄懂 Promise

發(fā)布時間:2025/3/12 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 promise链式调用_这一次,彻底弄懂 Promise 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Promise 必須為以下三種狀態(tài)之一:等待態(tài)(Pending)、執(zhí)行態(tài)(Fulfilled)和拒絕態(tài)(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其他任何狀態(tài)(即狀態(tài) immutable)。

基本過程:

  • 初始化 Promise 狀態(tài)(pending)
  • 執(zhí)行 then(..) 注冊回調(diào)處理數(shù)組(then 方法可被同一個 promise 調(diào)用多次)
  • 立即執(zhí)行 Promise 中傳入的 fn 函數(shù),將Promise 內(nèi)部 resolve、reject 函數(shù)作為參數(shù)傳遞給 fn ,按事件機(jī)制時機(jī)處理
  • Promise里的關(guān)鍵是要保證,then方法傳入的參數(shù) onFulfilled 和 onRejected,必須在then方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行。
  • 真正的鏈?zhǔn)絇romise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個promise.

    鏈?zhǔn)秸{(diào)用

    先從 Promise 執(zhí)行結(jié)果看一下,有如下一段代碼:

    new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) resolve({ test: 2 }) reject({ test: 2 }) }, 1000) }).then((data) => { console.log('result1', data) },(data1)=>{ console.log('result2',data1) }).then((data) => { console.log('result3', data) }) //result1 { test: 1 } //result3 undefined復(fù)制代碼

    顯然這里輸出了不同的 data。由此可以看出幾點:

  • 可進(jìn)行鏈?zhǔn)秸{(diào)用,且每次 then 返回了新的 Promise(2次打印結(jié)果不一致,如果是同一個實例,打印結(jié)果應(yīng)該一致。
  • 只輸出第一次 resolve 的內(nèi)容,reject 的內(nèi)容沒有輸出,即 Promise 是有狀態(tài)且狀態(tài)只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
  • then 中返回了新的 Promise,但是then中注冊的回調(diào)仍然是屬于上一個 Promise 的。
  • 基于以上幾點,我們先寫個基于 PromiseA+ 規(guī)范的只含 resolve 方法的 Promise 模型:

    function Promise(fn){ let state = 'pending'; let value = null; const callbacks = []; this.then = function (onFulfilled){ return new Promise((resolve, reject)=>{ handle({ //橋梁,將新 Promise 的 resolve 方法,放到前一個 promise 的回調(diào)對象中 onFulfilled, resolve }) }) } function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } if(state === 'fulfilled'){ if(!callback.onFulfilled){ callback.resolve(value) return; } const ret = callback.onFulfilled(value) //處理回調(diào) callback.resolve(ret) //處理下一個 promise 的resolve } } function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) //基于 PromiseA+ 規(guī)范 } function handelCb(){ while(callbacks.length) { const fulfiledFn = callbacks.shift(); handle(fulfiledFn); }; } fn(resolve) }復(fù)制代碼

    這個模型簡單易懂,這里最關(guān)鍵的點就是在 then 中新創(chuàng)建的 Promise,它的狀態(tài)變?yōu)?fulfilled 的節(jié)點是在上一個 Promise的回調(diào)執(zhí)行完畢的時候。也就是說當(dāng)一個 Promise 的狀態(tài)被 fulfilled 之后,會執(zhí)行其回調(diào)函數(shù),而回調(diào)函數(shù)返回的結(jié)果會被當(dāng)作 value,返回給下一個 Promise(也就是then 中產(chǎn)生的 Promise),同時下一個 Promise的狀態(tài)也會被改變(執(zhí)行 resolve 或 reject),然后再去執(zhí)行其回調(diào),以此類推下去...鏈?zhǔn)秸{(diào)用的效應(yīng)就出來了。

    但是如果僅僅是例子中的情況,我們可以這樣寫:

    new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething console.log('result3') }) //result1 { test: 1 } //result3復(fù)制代碼

    實際上,我們常用的鏈?zhǔn)秸{(diào)用,是用在異步回調(diào)中,以解決"回調(diào)地獄"的問題。如下例子:

    new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000)}).then((data) => { console.log('result1', data) //dosomething return test()}).then((data) => { console.log('result2', data)})function test(id) { return new Promise(((resolve) => { setTimeout(() => { resolve({ test: 2 }) }, 5000) }))}//基于第一個 Promise 模型,執(zhí)行后的輸出//result1 { test: 1 }//result2 Promise {then: ?}復(fù)制代碼

    用上面的 Promise 模型,得到的結(jié)果顯然不是我們想要的。認(rèn)真看上面的模型,執(zhí)行 callback.resolve 時,傳入的參數(shù)是 callback.onFulfilled 執(zhí)行完成的返回,顯然這個測試?yán)臃祷氐木褪且粋€ Promise,而我們的 Promise 模型中的 resolve 方法并沒有特殊處理。那么我們將 resolve 改一下:

    function Promise(fn){ ... function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){ const {then} = newValue if(typeof then === 'function'){ // newValue 為新產(chǎn)生的 Promise,此時resolve為上個 promise 的resolve //相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個 promise 的resolve 為其回調(diào) then.call(newValue,resolve) return } } state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) } ... }復(fù)制代碼

    用這個模型,再測試我們的例子,就得到了正確的結(jié)果:

    new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething return test() }).then((data) => { console.log('result2', data) }) function test(id) { return new Promise(((resolve, reject) => { setTimeout(() => { resolve({ test: 2 }) }, 5000) })) } //result1 { test: 1 } //result2 { test: 2 }復(fù)制代碼

    顯然,新增的邏輯就是針對 resolve 入?yún)?Promise 的時候的處理。我們觀察一下 test 里面創(chuàng)建的 Promise,它是沒有調(diào)用 then方法的。從上面的分析我們已經(jīng)知道 Promise 的回調(diào)函數(shù)就是通過調(diào)用其 then 方法注冊的,因此 test 里面創(chuàng)建的 Promise 其回調(diào)函數(shù)為空。

    顯然如果沒有回調(diào)函數(shù),執(zhí)行 resolve 的時候,是沒辦法鏈?zhǔn)较氯サ摹R虼?#xff0c;我們需要主動為其注入回調(diào)函數(shù)。

    我們只要把第一個 then 中產(chǎn)生的 Promise 的 resolve 函數(shù)的執(zhí)行,延遲到 test 里面的 Promise 的狀態(tài)為 onFulfilled 的時候再執(zhí)行,那么鏈?zhǔn)骄涂梢岳^續(xù)了。所以,當(dāng) resolve 入?yún)?Promise 的時候,調(diào)用其 then 方法為其注入回調(diào)函數(shù),而注入的是前一個 Promise 的 resolve 方法,所以要用 call 來綁定 this 的指向。

    基于新的 Promise 模型,上面的執(zhí)行過程產(chǎn)生的 Promise 實例及其回調(diào)函數(shù),可以用看下表:

    Promise callback P1 [{onFulfilled:c1(第一個then中的fn),resolve:p2resolve}] P2 (P1 調(diào)用 then 時產(chǎn)生) [{onFulfilled:c2(第二個then中的fn),resolve:p3resolve}] P3 (P2 調(diào)用 then 時產(chǎn)生) [] P4 (執(zhí)行c1中產(chǎn)生[調(diào)用 test ]) [{onFulfilled:p2resolve,resolve:p5resolve}] P5 (調(diào)用p2resolve 時,進(jìn)入 then.call 邏輯中產(chǎn)生) [] 有了這個表格,我們就可以清晰知道各個實例中 callback 執(zhí)行的順序是:

    c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []

    以上就是鏈?zhǔn)秸{(diào)用的原理了。

    reject

    下面我們再來補(bǔ)全 reject 的邏輯。只需要在注冊回調(diào)、狀態(tài)改變時加上 reject 的邏輯即可。

    完整代碼如下:

    function Promise(fn){ let state = 'pending'; let value = null; const callbacks = []; this.then = function (onFulfilled,onRejected){ return new Promise((resolve, reject)=>{ handle({ onFulfilled, onRejected, resolve, reject }) }) } function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected; const next = state === 'fulfilled'? callback.resolve:callback.reject; if(!cb){ next(value) return; } const ret = cb(value) next(ret) } function resolve(newValue){ const fn = ()=>{ if(state !== 'pending')return if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){ const {then} = newValue if(typeof then === 'function'){ // newValue 為新產(chǎn)生的 Promise,此時resolve為上個 promise 的resolve //相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個 promise 的resolve 為其回調(diào) then.call(newValue,resolve, reject) return } } state = 'fulfilled'; value = newValue handelCb() } setTimeout(fn,0) } function reject(error){ const fn = ()=>{ if(state !== 'pending')return if(error && (typeof error === 'object' || typeof error === 'function')){ const {then} = error if(typeof then === 'function'){ then.call(error,resolve, reject) return } } state = 'rejected'; value = error handelCb() } setTimeout(fn,0) } function handelCb(){ while(callbacks.length) { const fn = callbacks.shift(); handle(fn); }; } fn(resolve, reject) }復(fù)制代碼

    異常處理

    異常通常是指在執(zhí)行成功/失敗回調(diào)時代碼出錯產(chǎn)生的錯誤,對于這類異常,我們使用 try-catch 來捕獲錯誤,并將 Promise 設(shè)為 rejected 狀態(tài)即可。

    handle代碼改造如下:

    function handle(callback){ if(state === 'pending'){ callbacks.push(callback) return; } const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected; const next = state === 'fulfilled'? callback.resolve:callback.reject; if(!cb){ next(value) return; } try { const ret = cb(value) next(ret) } catch (e) { callback.reject(e); } }復(fù)制代碼

    我們實際使用時,常習(xí)慣注冊 catch 方法來處理錯誤,例:

    new Promise((resolve, reject) => { setTimeout(() => { resolve({ test: 1 }) }, 1000) }).then((data) => { console.log('result1', data) //dosomething return test() }).catch((ex) => { console.log('error', ex) })復(fù)制代碼

    實際上,錯誤也好,異常也罷,最終都是通過reject實現(xiàn)的。也就是說可以通過 then 中的錯誤回調(diào)來處理。所以我們可以增加這樣的一個 catch 方法:

    function Promise(fn){ ... this.then = function (onFulfilled,onRejected){ return new Promise((resolve, reject)=>{ handle({ onFulfilled, onRejected, resolve, reject }) }) } this.catch = function (onError){ this.then(null,onError) } ... }復(fù)制代碼

    Finally方法

    在實際應(yīng)用的時候,我們很容易會碰到這樣的場景,不管Promise最后的狀態(tài)如何,都要執(zhí)行一些最后的操作。我們把這些操作放到 finally 中,也就是說 finally 注冊的函數(shù)是與 Promise 的狀態(tài)無關(guān)的,不依賴 Promise 的執(zhí)行結(jié)果。所以我們可以這樣寫 finally 的邏輯:

    function Promise(fn){ ... this.catch = function (onError){ this.then(null,onError) } this.finally = function (onDone){ this.then(onDone,onDone) } ... }復(fù)制代碼

    resolve 方法和 reject 方法

    實際應(yīng)用中,我們可以使用 Promise.resolve 和 Promise.reject 方法,用于將于將非 Promise 實例包裝為 Promise 實例。如下例子:

    Promise.resolve({name:'winty'})Promise.reject({name:'winty'})// 等價于new Promise(resolve => resolve({name:'winty'}))new Promise((resolve,reject) => reject({name:'winty'}))復(fù)制代碼

    這些情況下,Promise.resolve 的入?yún)⒖赡苡幸韵聨追N情況:

    • 無參數(shù) [直接返回一個resolved狀態(tài)的 Promise 對象]
    • 普通數(shù)據(jù)對象 [直接返回一個resolved狀態(tài)的 Promise 對象]
    • 一個Promise實例 [直接返回當(dāng)前實例]
    • 一個thenable對象(thenable對象指的是具有then方法的對象) [轉(zhuǎn)為 Promise 對象,并立即執(zhí)行thenable對象的then方法。]

    基于以上幾點,我們可以實現(xiàn)一個 Promise.resolve 方法如下:

    function Promise(fn){ ... this.resolve = function (value){ if (value && value instanceof Promise) { return value; } else if (value && typeof value === 'object' && typeof value.then === 'function'){ let then = value.then; return new Promise(resolve => { then(resolve); }); } else if (value) { return new Promise(resolve => resolve(value)); } else { return new Promise(resolve => resolve()); } } ... }復(fù)制代碼

    Promise.reject與Promise.resolve類似,區(qū)別在于Promise.reject始終返回一個狀態(tài)的rejected的Promise實例,而Promise.resolve的參數(shù)如果是一個Promise實例的話,返回的是參數(shù)對應(yīng)的Promise實例,所以狀態(tài)不一 定。 因此,reject 的實現(xiàn)就簡單多了,如下:

    function Promise(fn){ ... this.reject = function (value){ return new Promise(function(resolve, reject) {reject(value);}); } ... }復(fù)制代碼

    Promise.all

    入?yún)⑹且粋€ Promise 的實例數(shù)組,然后注冊一個 then 方法,然后是數(shù)組中的 Promise 實例的狀態(tài)都轉(zhuǎn)為 fulfilled 之后則執(zhí)行 then 方法。這里主要就是一個計數(shù)邏輯,每當(dāng)一個 Promise 的狀態(tài)變?yōu)?fulfilled 之后就保存該實例返回的數(shù)據(jù),然后將計數(shù)減一,當(dāng)計數(shù)器變?yōu)?0 時,代表數(shù)組中所有 Promise 實例都執(zhí)行完畢。

    function Promise(fn){ ... this.all = function (arr){ var args = Array.prototype.slice.call(arr); return new Promise(function(resolve, reject) { if(args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { try { if(val && (typeof val === 'object' || typeof val === 'function')) { var then = val.then; if(typeof then === 'function') { then.call(val, function(val) { res(i, val); }, reject); return; } } args[i] = val; if(--remaining === 0) { resolve(args); } } catch(ex) { reject(ex); } } for(var i = 0; i < args.length; i++) { res(i, args[i]); } }); } ... }復(fù)制代碼

    Promise.race

    有了 Promise.all 的理解,Promise.race 理解起來就更容易了。它的入?yún)⒁彩且粋€ Promise 實例數(shù)組,然后其 then 注冊的回調(diào)方法是數(shù)組中的某一個 Promise 的狀態(tài)變?yōu)?fulfilled 的時候就執(zhí)行。因為 Promise 的狀態(tài)只能改變一次,那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對象的 resolve 方法,注入到數(shù)組中的每一個 Promise 實例中的回調(diào)函數(shù)中即可。

    function Promise(fn){ ... this.race = function(values) { return new Promise(function(resolve, reject) { for(var i = 0, len = values.length; i < len; i++) { values[i].then(resolve, reject); } }); } ... } 復(fù)制代碼

    總結(jié)

    Promise 源碼不過幾百行,我們可以從執(zhí)行結(jié)果出發(fā),分析每一步的執(zhí)行過程,然后思考其作用即可。其中最關(guān)鍵的點就是要理解 then 函數(shù)是負(fù)責(zé)注冊回調(diào)的,真正的執(zhí)行是在 Promise 的狀態(tài)被改變之后。而當(dāng) resolve 的入?yún)⑹且粋€ Promise 時,要想鏈?zhǔn)秸{(diào)用起來,就必須調(diào)用其 then 方法(then.call),將上一個 Promise 的 resolve 方法注入其回調(diào)數(shù)組中。

    補(bǔ)充說明

    雖然 then 普遍認(rèn)為是微任務(wù)。但是瀏覽器沒辦法模擬微任務(wù),目前要么用 setImmediate ,這個也是宏任務(wù),且不兼容的情況下還是用 setTimeout 打底的。還有,promise 的 polyfill (es6-promise) 里用的也是 setTimeout。因此這里就直接用 setTimeout,以宏任務(wù)來代替微任務(wù)了。

    參考資料

    • PromiseA+規(guī)范
    • Promise 實現(xiàn)原理精解
    • 30分鐘,讓你徹底明白Promise原理

    完整 Promise 模型

    function Promise(fn) { let state = 'pending' let value = null const callbacks = [] this.then = function (onFulfilled, onRejected) { return new Promise((resolve, reject) => { handle({ onFulfilled, onRejected, resolve, reject, }) }) } this.catch = function (onError) { this.then(null, onError) } this.finally = function (onDone) { this.then(onDone, onError) } this.resolve = function (value) { if (value && value instanceof Promise) { return value } if (value && typeof value === 'object' && typeof value.then === 'function') { const { then } = value return new Promise((resolve) => { then(resolve) }) } if (value) { return new Promise(resolve => resolve(value)) } return new Promise(resolve => resolve()) } this.reject = function (value) { return new Promise(((resolve, reject) => { reject(value) })) } this.all = function (arr) { const args = Array.prototype.slice.call(arr) return new Promise(((resolve, reject) => { if (args.length === 0) return resolve([]) let remaining = args.length function res(i, val) { try { if (val && (typeof val === 'object' || typeof val === 'function')) { const { then } = val if (typeof then === 'function') { then.call(val, (val) => { res(i, val) }, reject) return } } args[i] = val if (--remaining === 0) { resolve(args) } } catch (ex) { reject(ex) } } for (let i = 0; i < args.length; i++) { res(i, args[i]) } })) } this.race = function (values) { return new Promise(((resolve, reject) => { for (let i = 0, len = values.length; i < len; i++) { values[i].then(resolve, reject) } })) } function handle(callback) { if (state === 'pending') { callbacks.push(callback) return } const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected const next = state === 'fulfilled' ? callback.resolve : callback.reject if (!cb) { next(value) return } try { const ret = cb(value) next(ret) } catch (e) { callback.reject(e) } } function resolve(newValue) { const fn = () => { if (state !== 'pending') return if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { const { then } = newValue if (typeof then === 'function') { // newValue 為新產(chǎn)生的 Promise,此時resolve為上個 promise 的resolve // 相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個 promise 的resolve 為其回調(diào) then.call(newValue, resolve, reject) return } } state = 'fulfilled' value = newValue handelCb() } setTimeout(fn, 0) } function reject(error) { const fn = () => { if (state !== 'pending') return if (error && (typeof error === 'object' || typeof error === 'function')) { const { then } = error if (typeof then === 'function') { then.call(error, resolve, reject) return } } state = 'rejected' value = error handelCb() } setTimeout(fn, 0) } function handelCb() { while (callbacks.length) { const fn = callbacks.shift() handle(fn) } } fn(resolve, reject)}

    總結(jié)

    以上是生活随笔為你收集整理的promise链式调用_这一次,彻底弄懂 Promise的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。