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