摘要:方法沒有設(shè)置返回值。解決思路是,當(dāng)遇到任務(wù)的返回值是一個或者,并且有自己的方法的時候,就將它當(dāng)做是一個對象處理,等這個對象中的方法處理到的時候,把作為參數(shù)輸出傳遞給后續(xù)的任務(wù)。
前段時間看到關(guān)于microTask的文章,《Tasks, microTasks, queues and schedules》,感覺有必要澄清一下。本篇里用setTimeout來實現(xiàn)的Promise,和瀏覽器原生的Promise是有本質(zhì)區(qū)別的。多數(shù)時候感覺不到差異,但正如文章所說,如果不搞清楚microTasks,在實戰(zhàn)中一旦遇到和這家伙有關(guān)的問題,真得會一點方向都沒有。"Yeah, it"ll bite you in obscure places (ouch). "推薦一讀。
前兩天看到前端早讀課的一篇“【第666期】剖析?Promise 內(nèi)部機(jī)制”,從實現(xiàn)角度講解promise底層實現(xiàn)原理的。看得我兩天里寢食難安,腦袋疼胸口悶,嚴(yán)重懷疑腦洞太小。終于還是把邏輯理順了。短短幾十行代碼威力如此驚人,逼得我把洪荒之力都用完了……
趁熱打鐵把自己的理解記錄一下~
要弄清原理,必須要非常清楚Promise想實現(xiàn)什么。
比如現(xiàn)在有兩件事情,第一件事情是要洗衣服,第二件事情是晾衣服。
function wash(){ console.log("開始洗衣服..."); setTimeout(()=>{ console.log("洗完了!"); return "一堆洗干凈的衣服"; }, 2000); } function hang(clothes){ console.log("開始晾衣服..."); /*...晾衣服中...*/ console.log(clothes+"晾完了!"); }
晾衣服hang(clothes)是一定要等洗衣服wash()結(jié)束,然后接收到洗好了的衣服clothes以后,才能執(zhí)行的。
類似這種有明確先后執(zhí)行順序,并且可能會有依賴關(guān)系的(沒有前面執(zhí)行完返回的結(jié)果后面就處理不下去)場景,就是Promise的用武之地。
先看下怎么用Promise來完成先洗衣服后晾干這兩步的。
第一步:
var promise = new Promise(wash);
告訴Promise立刻執(zhí)行wash開始洗衣服。
第二步:
promise.then(hang);
告訴Promise等wash結(jié)束以后執(zhí)行hang開始晾衣服。
Promise用起來就是這么簡單!這么清爽!
如果等晾干以后還要收衣服的話,就繼續(xù)再后面加:
promise.then(hang).then(pickup); //pickup就是收衣服方法,等后面再實現(xiàn)
那么問題來了,promise怎么知道衣服啥時候洗完?
Promise規(guī)定,丟給Promise執(zhí)行的方法,需要將一個方法(resolve)作為參數(shù),在執(zhí)行完成以后,將返回的結(jié)果傳給這個resolve方法。(有點難說清楚,上代碼試試)
這樣就需要改寫wash方法
function wash(resolve){ console.log("開始洗衣服..."); setTimeout(()=>{ console.log("洗完了!"); resolve("一堆洗干凈的衣服"); }, 2000); }
resolve("一堆干凈的衣服")會調(diào)用Promise里的resolve方法,Promise就會知道洗衣服wash操作已經(jīng)成功完成了,可以接下去處理then后面的事情了。
把事情變得稍微復(fù)雜點試試。衣服洗完了要晾出去,晾完以后要等曬干,曬干了以后要收衣服。我們需要重寫hang方法并新增dry和pickup方法
function hang(clothes){ console.log("開始晾衣服..."); /*...晾衣服中...*/ console.log(clothes+"晾好了!"); return "一堆晾好的衣服"; } function dry(clothes){ console.log("等衣服干..."); /*...晾干中...*/ console.log(clothes+"晾干了!"); return "一堆晾干的衣服"; } function pickup(clothes){ console.log("開始收衣服..."); /*...收衣服中...*/ console.log(clothes+"收完了!"); }
對比之前的代碼,發(fā)現(xiàn)多了一行return語句,將處理后得到的結(jié)果輸出,作為參數(shù)傳入接下去的要處理的方法。
準(zhǔn)備好了wash,hang,dry,pickup四個方法后,執(zhí)行一下看看:
var promise = new Promise(wash); promise.then(hang).then(dry).then(pickup);
輸出結(jié)果如下:
開始洗衣服... 洗完了,去晾干! 開始晾衣服... 一堆洗干凈的衣服晾完了! 等衣服干... 一堆晾好的衣服晾干了! 開始收衣服... 一堆晾干了的衣服收完了!
再稍微復(fù)雜點兒試試~
上面輸出結(jié)果可以看到,從’開始晾衣服...’到’一堆晾干了的衣服收完了!’幾乎是同時輸出的。每個動作都應(yīng)該有段時間間隔才對呀~再改
function hang(clothes){ console.log("開始晾衣服..."); setTimeout(()=>{ console.log(clothes+"晾完了!"); }, 3000); return ("一堆晾好的衣服"); } function dry(clothes){ console.log("等衣服干..."); setTimeout(()=>{ console.log(clothes+"晾干了!"); }, 3000); return ("一堆晾干了的衣服"); } function pickup(clothes){ console.log("開始收衣服..."); setTimeout(()=>{ console.log(clothes+"收完了!"); }, 3000) }
執(zhí)行一下看看……
開始洗衣服... 洗完了! 開始晾衣服... 等衣服干... 開始收衣服... 一堆洗干凈的衣服晾完了! 一堆晾好的衣服晾干了! 一堆晾干了的衣服收完了!
問題比較明顯:
第一.順序亂了
第二.從’洗完了’到’開始收衣服...’同時輸出
第三.最后三句話隔了3秒后同時輸出
可以看出then執(zhí)行的方法并不會等setTimeout執(zhí)行完才去執(zhí)行接下去的then中的方法,因為then執(zhí)行的方法都是同步的。咋辦呢?再改~
function hang(clothes){ console.log("開始晾衣服..."); return new Promise(resolve=>{ setTimeout(()=>{ console.log(clothes+"晾完了!"); resolve("一堆晾好的衣服"); }, 3000) }); } function dry(clothes){ console.log("等衣服干..."); return new Promise(resolve=>{ setTimeout(()=>{ console.log(clothes+"晾干了!"); resolve("一堆晾干了的衣服"); }, 3000) }); } function pickup(clothes){ console.log("開始收衣服..."); setTimeout(()=>{ console.log(clothes+"收完了!"); }, 3000) }
hang和dry方法返回值改成了一個Promise對象。這里有點難理解,then傳入的方法如果返回的是個Promise對象,那么再后面的then傳入的方法就會等到這個Promise(實際上是傳入Promise的方法)調(diào)用了resolve()為止,才會繼續(xù)執(zhí)行。
Promise原理最頭疼的部分來了,看看new Promise(wash).then(hang).then(dry).then(pickup)到底怎么實現(xiàn)的。
首先new Promise(wash):
實例化Promise并傳入一個方法,這個方法就立刻開始執(zhí)行了,所以Promise里會執(zhí)行wash(resolve)方法;
wash方法中通過調(diào)用resolve(‘一堆洗干凈的衣服’)通知Promise自己執(zhí)行完了,所以Promise里會有一個resolve(_result_value)方法處理wash的返回結(jié)果;
再看then(hang):
then是Promise的實例方法,所以Promise里會有一個this.then = function(mission){...}實例方法。
再根據(jù)Promise的實現(xiàn)結(jié)果,即then后面的方法要等到wash中執(zhí)行到resolve(‘一堆洗干凈的衣服’)以后才能開始執(zhí)行。實現(xiàn)方法就是在調(diào)用then(hang)的時候,不直接執(zhí)行hang方法,而是把hang方法存起來,由resolve(_result_value)來觸發(fā)。
function myPromise(fn){ const missions = [];//待執(zhí)行隊列 var value = null; //執(zhí)行傳入的方法 fn(resolve); //當(dāng)傳入的方法中調(diào)用resolve(value)時,異步執(zhí)行mission function resolve(_return_value){ value = _return_value; missions.forEach(mission=>{ mission(value); }); } //執(zhí)行then方法時,將傳入的方法加入missions,等待resolve觸發(fā)。 this.then = function(mission){ missions.push(mission); } }
同時修改一下wash方法
function wash(resolve){ console.log("開始洗衣服..."); console.log("洗完了!"); resolve("一堆洗干凈的衣服"); }
執(zhí)行new myPromise(wash).then(hang);
輸出結(jié)果:
開始洗衣服... 洗完了!
晾衣服動作沒執(zhí)行~
來看一下發(fā)生了什么
new myPromise(wash)觸發(fā)執(zhí)行wash(resolve)方法=>wash(resolve)觸發(fā)執(zhí)行resolve(‘一堆洗干凈的衣服’)=>resolve(_return_value)執(zhí)行mission(value)……等下,還沒執(zhí)行then(hang)之前missions里還沒任務(wù)呢!
所以需要改下resolve方法
function resolve(_return_value){ value = _return_value; setTimeout(()=>{ missions.forEach(mission=>{ mission(value); }) }, 0); }
在執(zhí)行一下new myPromise(wash).then(hang);結(jié)果就對了。
增加狀態(tài)控制如果我們想這樣使用:
var promise = new myPromise(wash); setTimeout(()=>{ promise.then(hang) }, 1000)
開始洗衣服以后干別的事情去了,過一段時間回來如果洗完了就直接晾衣服,沒洗完就接著等待。
因為hang也是個異步操作,會延遲到mission(value)之后才執(zhí)行,所以此時myPromise又沒法正常工作了。
解決辦法是給myPromise增加一個狀態(tài)state。當(dāng)沒有觸發(fā)resovle(_return_value)時,狀態(tài)處在pending處理中;當(dāng)觸發(fā)了resovle(_return_value)時,狀態(tài)置為fulfilled已處理。而this.then = function(mission){...}在處理前先對狀態(tài)做個判斷,pending時將mission插入missions任務(wù)隊列,fulfilled時就直接執(zhí)行mission(value)。
function resolve(_return_value){ state = "fulfilled"; ...//省略其他未改動代碼 } this.then = function(mission){ if(state === "fulfilled"){ mission(value); }else{ missions.push(mission); } }任務(wù)鏈處理
目前執(zhí)行new myPromise(wash).then(hang).then(dry).then(pickup)會報錯。then方法沒有設(shè)置返回值。稍微調(diào)整下代碼
this.then = function(mission){ if(state === "fulfilled"){ mission(value); }else{ missions.push(mission); } return this; }
簡化hang, dry, pickup方法
function hang(clothes){ console.log("開始晾衣服..."); console.log(clothes+"晾完了!"); return ("一堆晾好的衣服"); } function dry(clothes) { console.log("等衣服干..."); console.log(clothes + "晾干了!"); return ("一堆晾干了的衣服"); } function pickup(clothes){ console.log("開始收衣服..."); console.log(clothes+"收完了!"); }
執(zhí)行new myPromise(wash).then(hang).then(dry).then(pickup),錯是不報了。但是then之間沒有正常傳遞返回的值。clothes始終是“一堆洗干凈的衣服”。
myPromise中的value是在執(zhí)行resolve(_return_value)時賦值的。一個myPromise對象只有一個初始任務(wù)(這里是wash),初始任務(wù)就執(zhí)行了一次resovle(‘一堆洗干凈的衣服’)。而所有的then方法返回的都是同一個myPromise對象,所以value指向的都是同一個值。
解決思路是,每次調(diào)用then方法后,返回一個新的myPromise對象 new myPromise(fn);在fn中執(zhí)行then方法中要執(zhí)行的操作。
this.then = function(mission){ function fn(resolve){ if(state === "pending"){ missions.push(mission) }else{ const result = mission(value); resolve(result);//關(guān)鍵! } } return new myPromise(fn); }
當(dāng)觸發(fā)mission(value)時,將返回的結(jié)果作為result執(zhí)行resolve(result),這就將result傳遞給了下一個myPromise。
then直接觸發(fā)mission(value)執(zhí)行的操作和resolve(_result_value)是一樣的,所以resolve也要調(diào)整
function resolve(_return_value){ value = _return_value; state = "fulfilled"; setTimeout(()=>{ missions.forEach(mission=>{ const result = mission(value); resolve(result); //死循環(huán) }) }, 0); }
執(zhí)行下?死循環(huán)!resolve中應(yīng)該調(diào)用的是then創(chuàng)建的新myPromise的resolve方法,而不是他本身。所以then方法必須把自己創(chuàng)建的myPromise的resolve傳遞出來。
var next_resolve = null;//保存then生成的下一個myPromise的resolve方法 this.then = function(mission){ function fn(resolve){ next_resolve = resolve; if(state === "pending"){ missions.push(mission) }else{ const result = mission(value); resolve(result); } } return new myPromise(fn); } function resolve(_return_value){ value = _return_value; state = "fulfilled"; setTimeout(()=>{ missions.forEach(mission=>{ const result = mission(value); next_resolve(result); }) }, 0); }
執(zhí)行下代碼看看結(jié)果吧~
Promise對象傳遞最后一個問題~(最后一關(guān)邏輯有點繞)
還記得之前用Promise對象來作為hang()和dry()的返回值的場景嗎?hang()返回Promise對象后,Promise中resolve(‘一堆晾好的衣服’)被執(zhí)行后,才會將’一堆晾好的衣服’作為參數(shù)傳遞給dry(clothes)方法并開始執(zhí)行。
這種情況下,用前面寫好的myPromise執(zhí)行一下,又不對了。dry()和pickup()的形參變成了一個new myPromise,結(jié)果肯定出錯嘛。
解決思路是,當(dāng)遇到任務(wù)的返回值是一個object或者function,并且有自己的then方法的時候,就將它當(dāng)做是一個Promise對象處理,等這個Promise對象中的方法處理到resolve(_return_result)的時候,把_return_result作為參數(shù)輸出傳遞給后續(xù)的任務(wù)。
重寫then和resolve
this.then = function(mission){ var fn = function(resolve){ next_resolve = resolve; if(state === "pending"){ missions.push(mission) }else{ const result = mission(value); if(result && (typeof result == "object" || typeof result == "function")){ if(result.then && typeof result.then == "function"){ result.then(next_resolve);//關(guān)鍵! } }else{ next_resolve(result); } } } return new myPromise(fn); } function resolve(_return_value){ value = _return_value; state = "fulfilled"; setTimeout(()=>{ missions.forEach(mission=>{ const result = mission(value); if(result && (typeof result == "object" || typeof result == "function")){ if(result.then && typeof result.then == "function"){ result.then(next_resolve);//關(guān)鍵! } }else{ next_resolve(result); } }) }, 0); }
有必要解釋一下result.then(next_resolve)。result此時是一個Promise對象(取名resultPromise),我們需要在resultPromise對象方法執(zhí)行到resolve(_result_value)時,獲取到_result_value并傳遞給next_resolve(result)執(zhí)行。
而then方法正是干這事兒的:將next_resolve放進(jìn)resultPromise的執(zhí)行隊列missions里,resultPromise執(zhí)行resolve(_result_value),state狀態(tài)變?yōu)閒ulfilled,觸發(fā)執(zhí)行next_resolve(_result_value)。后面就是next_promise狀態(tài)變?yōu)閒ulfilled,觸發(fā)執(zhí)行接下去的mission...
最后把公共代碼提取出來整理一下:
function myPromise(fn){ const missions = []; //待執(zhí)行隊列 var value = null; var state = "pending"; var next_resolve = null; //執(zhí)行傳入的方法 fn(resolve); //當(dāng)傳入的方法中調(diào)用resolve(value)時,異步執(zhí)行mission function resolve(_return_value){ value = _return_value; state = "fulfilled"; setTimeout(()=>{ missions.forEach(mission=>{ handle(mission); }) }, 0); } //執(zhí)行then方法時,將傳入的方法加入missions,等待resolve觸發(fā)。 this.then = function(mission){ var fn = function(resolve){ next_resolve = resolve; if(state === "pending"){ missions.push(mission) }else{ handle(mission); } } return new myPromise(fn); } function handle(mission){ const result = mission(value); //當(dāng)處理結(jié)果為Promise對象時,將next_resolve推入待執(zhí)行隊列 if(result && (typeof result == "object" || typeof result == "function")){ if(result.then && typeof result.then == "function"){ result.then(next_resolve); } }else{ next_resolve(result); } } }
代碼和前端早讀課的“剖析?Promise 內(nèi)部機(jī)制”稍有區(qū)別,但個人覺得這樣寫邏輯更清晰一些。至于promise的reject部分,偷懶省略啦~ 當(dāng)是留點思考空間咯~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/87856.html
摘要:主要邏輯本質(zhì)上還是回調(diào)函數(shù)那一套。通過的判斷完成異步和同步的區(qū)分。 主要邏輯: 本質(zhì)上還是回調(diào)函數(shù)那一套。通過_subscribers的判斷完成異步和同步的區(qū)分。通過 resolve,reject -> publish -> invokeCallback -> resolve,reject的遞歸和下一條then的parent是上一條的child來完成then鏈的流轉(zhuǎn) 同步情況...
摘要:是什么可以理解為一個承諾,如果調(diào)用,返回一個承諾給,然后就可以在寫計劃的時候這么寫,當(dāng)返回結(jié)果的時候,就執(zhí)行方案,如果沒有返回要的結(jié)果,就執(zhí)行方案。這樣一來,所有的潛在風(fēng)險就都在的可控范圍之內(nèi)了。 promise是什么 Promise可以理解為一個承諾,如果A調(diào)用B,B返回一個承諾給A,然后A就可以在寫計劃的時候這么寫,當(dāng)B返回結(jié)果的時候,A就執(zhí)行方案1,如果B沒有返回A要的結(jié)果,A就...
摘要:如題,新增的真的簡單易用,感覺現(xiàn)在這一個支持完全可行。雖然兼容性問題還是存在,但是打上后就基本解決了。來自使用簡單使用這里說明一下,必須配合一起使用,這會得到更佳效果。 如題,es6 新增的fetch真的簡單易用,感覺現(xiàn)在這一個支持完全可行。 showImg(https://segmentfault.com/img/bVGlRy?w=995&h=631); 雖然兼容性問題還是存在,但是...
摘要:下一步準(zhǔn)備使用網(wǎng)易云代替音樂。已經(jīng)開發(fā)新的網(wǎng)易云代替音樂了,需要的可以看看這篇文章為微信小程序開發(fā)的網(wǎng)易云音樂庫 項目要做一個可以為日記添加音樂的小程序,所以要用到音樂api,參考了一些文章后我們封裝了一個qq音樂api庫(完成了動態(tài)token獲取,音樂搜索,音樂專輯圖片,音樂名稱,歌手名稱,播放),有需要的可以到Github自提。 小程序qq音樂api庫Gihub地址https://...
摘要:最近做了一個翻書效果的項目來總結(jié)一下實現(xiàn)過程和遇到的一些問題供自己以后快速解決問題希望也能幫到同樣遇到此類問題的同學(xué)如果有更好的方法希望你能分享給我地址插件問題都是些自己覺的比較難解決的比較片面如有其他疑問可以留言交流或者當(dāng)你從官網(wǎng)下載 最近做了一個翻書效果的項目, 來總結(jié)一下實現(xiàn)過程和遇到的一些問題, 供自己以后快速解決問題, 希望也能幫到同樣遇到此類問題的同學(xué), 如果有更好的方法,...
閱讀 1167·2021-10-20 13:48
閱讀 2173·2021-09-30 09:47
閱讀 3104·2021-09-28 09:36
閱讀 2342·2019-08-30 15:56
閱讀 1195·2019-08-30 15:52
閱讀 2020·2019-08-30 10:48
閱讀 607·2019-08-29 15:04
閱讀 564·2019-08-29 12:54