摘要:前言本文作者站在自己的角度深入淺出算了別這么裝逼分析在設計過程中通過構建異步數據流的思路。看上去那是相當的完美,根據咱們寫代碼的思路咱們來比對一下原版吧。時直接返回傳入函數函數。
前言
本文作者站在自己的角度正文深入淺出...算了別這么裝逼分析 redux applyMiddleware 在設計過程中通過 compose 構建異步數據流的思路。自己假設的一些場景幫助理解,希望大家在有異步數據流并且使用redux的過程中能夠有自己的思路(脫離thunk or saga)構建自己的 enhancer.如果你看完本文之后還想對我有更多的了解,可以移步我的github;
實際場景中遇到一個這樣的問題:商品詳情頁的微信頁面,未注冊的用戶點擊購買一個商品,我們希望能夠實現靜默登錄就有如下幾個步驟:
獲取code;
獲取openId、AccessToken;
根據openId、獲取openId、AccessToken;獲取用戶信息實現自動注冊然后登錄;
跳到商品購買頁。
這是就是一個典型異步數據流的過程。在上一個函數執行到某個時候再去調用下一個函數,使得這些個函數能夠順序執行。我們簡化一下,構建如下的函數數組使得他們能夠順序執行吧:
const fucArr = [ next=>{ setTimeout(()=>{ console.log(1); next() }, 300) }, next=>{ setTimeout(()=>{ console.log(2); next() }, 200) }, next=>{ setTimeout(()=>{ console.log(3); next() }, 100) } ]
擼起袖子就開始干了起來,有三個函數,基于走一步看一步思想(瞎胡說的)那我就先執行兩個吧
fucArr[0]( fucArr[1] );// funcArr[1] 運行報錯 TypeError: next is not a function
報錯,因為fucArr[1]中有next函數調用,也得接收一個函數,這下就麻煩了,fucArr[1]又不能直接傳參調用(因為會比fucArr[0]先執行),于是乎我們需要婉轉一點。
fucArr[0]( ()=>fucArr[1](()=>{}) ); //1 2
兩個函數順序執行搞定了那三個函數豈不是,沒錯,小case。
fucArr[0]( ()=>fucArr[1](()=>{ fucArr[2](()=>{}) }) );// 1 2 3
那我想在數組后面再加一個函數內心os:不加,去死,這樣寫下去真是要沒玩沒了了;
既然是個數組,那咱們就循環吧,思路肯定是:1.下個函數重新整合一下,作為參數往上一個函數傳;2.當到遍歷到數組末尾的時候傳入一個空函數進去避免報錯。
OK開始,既然是循環那就來個for循環吧,既然是下一個函數傳給上一個當參數,得讓相鄰的兩個函數出現在同一個循環里啦。于是有了起手式:
for (let index = 0; index < fucArr.length; index++) { const current = array[index]; const next = array[index + 1]; current(()=>next()) }
起手后發現不對呀,我需要喝口熱水,壓壓驚,冷靜一下,仔細觀察一下上面咱們代碼的結構發現咱們的函數結構其實是醬紫的:
a(()=>{ b(c) })
實際就上上一個函數調用被 ()=> 包裹后的下一個函數直接調用并傳入一個函數c,而函數c會在函數b的運行的某個時刻被調用,并且能接收下一個函數作為參數然后......再說下去就沒玩沒了了,因此c函數的模式其實也是被一個()=>{}包裹住的函數;然后再觀察我們上面的模式沒有c傳遞,因此模式應該是:
a(c=>{ b(c) }) // 我們再往下寫一層 a( d=>{ ( c=>b(c) )( d=>c(d) )// 為了避免你們看不懂我在寫啥,我告訴你你,這玩意兒是函數自調用 } ) // 怎么樣是不是有一種豁然開朗的趕腳
我們發現每次新加入一個函數,都是重新構建一次a函數里的參數,以下我將這個參數簡稱函數d
于是乎我們來通過循環構建這個d
為了讓循環體都能拿到d,因此它肯定是在循環的上層作用域
而且d具有兩個特性:
能接受一個函數作為參數,這個函數還能接收另一個函數作為參數,并會在某個時刻進行調用
每次循環都會根據當前d,然后加入當前函數,按照相同模式進行重構;
ps: 我們發現這兩個特性其實和咱們傳入的每個函數特性是一致的。
于是乎咱們把第一個數組的函數組作為起始函數:
var statusRecord = fucArr[0]; for (let index = 1; index < fucArr.length; index++) { statusRecord = next=>statusRecord(()=>fucArr[index](next)) }
寫完發現這樣是錯誤的,如果調用函數statusRecord那就會變成,自己調自己,自己調自己,自己調自己,自己調自己~~皮一下很開心~~...的無限遞歸。 在循環記錄當前狀態的場景下,有一個經典的demo大家了解過:在一個li列表中注冊點擊事件,點擊后alert出當前index;具體就不詳述了于是statusRecord,就改寫成了下面這樣
statusRecord = ((statusRecord)=>(next)=>statusRecord(()=>fucArr[index](next))(statusRecord))
為什么index不傳呢?因為index是let定義,可以看做塊級作用域,又有人要說js沒有塊級作用域,我:你說得對,再見。 最后咱們得到的還是這個模型要調用,別忘了傳入一個函數功最后數組最后一個函數調用。不然會報錯
statusRecord(()=>{}) // 輸出1、2、3
那咱們的功能就此實現了;不過可以優化一哈。咱們上面的代碼有幾個要素:
數組循環
狀態傳遞
初始狀態為數組的第一個元素
最終需要拿到單一的返回值
不就是活脫脫用來描述reduce的嗎?于是乎我們可以這樣擼
//pre 前一個狀態、 cur當前循環函數、next 待接收的下一個 fucArr.reduce((pre, cur)=>{ return (next)=>pre(()=>cur(next)) })(()=>{})// 1 2 3
以上異步順序調用的問題咱們已經理解了,咱們依次輸出了1,2,3。但是咱們現實業務中常常是下一個函數執行,和上一個函數執行結果是關聯的。咱們就想能不能改動題目貼合實際場景,上一個函數告訴下一個函數`console.log(n)`,于是乎題目做了一個小調整。
const fucArr = [ next=>{ setTimeout(()=>{ console.log(1); next(2) }, 300) }, // 函數2 (next,n)=>{ console.log(n); next(3) }, // 函數3 (next,n)=>{ console.log(n); next(4) } ] fucArr.reduce((pre,cur)=>{ return (next)=>pre((n)=>cur(next,n)) })((n)=>{console.log(n)})// 1 2 3 4
哇,功能又實現了,我們真棒。現在我們來回憶一下redux里中間件里傳入函數格式
store=>next=>action=>{ // dosomething... next() }
在某一步中store會被剝掉,在這就不細說了,于是咱們題目再變個種
const fucArr = [ next=>n=>{ setTimeout(()=>{ console.log(n); next(n+1) }, 300) }, // 函數2 next=>n=>{ setTimeout(()=>{ console.log(n); next(n+1) }, 300) }, // 函數3 next=>n=>{ setTimeout(()=>{ console.log(n); next(n+1) }, 300) } ]
臥槽,我們發現之于之前遇到的問題,這個實現就舒服很多了。因為你傳入的函數應該是直接調用,因為我們需要的調用的函數體其實是傳入函數調用后返回的那個函數,不需要我們通過()=>{...}這種額外的包裝。
于是咱們的實現就變成了:
fucArr.reduce((pre,cur)=>{ return (next)=>pre(cur(next)) })((n)=>{console.log(n)})
我們自信滿滿的node xxx.js了一下發現?????what fuck 為啥什么都沒有輸出,喝第二口水壓壓驚分析一下:
// before 之前的第一個函數和函數模型 next=>{ setTimeout(()=>{ console.log(1); next(n+1) }, 300) } a(c=>{ b(c) }) // ------------ // after 現在的第一個函數和函數模型 next=>n=>{ setTimeout(()=>{ console.log(n); next(n+1) }, 300) } a(b(c)) // 發現現在的第一個函數調用之后,一個函數。這個函數還要再接收一個參數去啟動
(⊙v⊙)嗯沒錯,經過精妙的分析我知道要怎么做了。
fucArr.reduce((pre,cur)=>{ return (next)=>pre(cur(next)) })((n)=>{console.log(n)})(1)// 1 2 3 4
我們來把這個功能包裝成方法,就叫他compose好了。
const compose = fucArr=>{ if(fucArr.length === 0) return; if(fucArr.length === 1) return fucArr[0]((n)=>{console.log(n)})(1) fucArr.reduce((pre,cur)=>{ return (next)=>pre(cur(next)) })((n)=>{console.log(n)})(1) }
看上去那是相當的完美,根據咱們寫代碼的思路咱們來比對一下原版吧。
length === 0 時: 返回一個傳入什么返回什么的函數。
length === 1 時: 直接返回傳入函數函數。
length > 1 時: 構建一個a(b(c(....)))這種函數調用模型并返回,使用者自定義最后一環需要運行的函數,并且能夠定義進入第一環的初始參數
// 原版 function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }結語
最后說一點題外話,在整個實現的過程中確保異步調用順序還有很多方式。親測可用的方式有:
bind
遞歸調用
通過new Promise 函數,將resolve作為參數方法傳入上一個函數然后改變Promise狀態...,
如果大家有興趣可以自己實現一下,為了不把大家的思路帶歪,在寫的過程中并沒有體現出來。
感謝@MrTreasure幫我指出文章中的問題,如果覺得我寫對你有一定的幫助,那就點個贊吧,因為您的鼓勵是我最大的動力。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100310.html
摘要:好處就是不再需要能夠處理異步的中間件了。不過,它是一個研究中間件很好的范本。執行它,返回的是由第二層函數組成的中間件數組。也就是說呀同學們,除了最后一個中間件的是原始的之外,倒數往前的中間件傳入的都是上一個中間件的邏輯函數。 本文是『horseshoe·Redux專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 獲得無與倫比的閱...
摘要:的中間件主要是通過模塊實現的。返回的也是一個對象這個其實就是,各個中間件的最底層第三層的哪個函數組成的圓環函數構成的這就是對源碼的一個整體解讀,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 applyMiddleware源碼解析 中間件機制在redux中是強大且便捷的,利用redux的中間件我們能夠實現日志記錄,異步調用等多種十分實用的功能。redux的中間件主要是...
摘要:執行完后,獲得數組,,它保存的對象是圖中綠色箭頭指向的匿名函數,因為閉包,每個匿名函數都可以訪問相同的,即。是函數式編程中的組合,將中的所有匿名函數,,組裝成一個新的函數,即新的,當新執行時,,從左到右依次執行所以順序很重要。 前言 It provides a third-party extension point between dispatching anaction, and t...
摘要:調用鏈中最后一個會接受真實的的方法作為參數,并借此結束調用鏈。總結我們常用的一般是除了和之外的方法,那個理解明白了,對于以后出現的問題會有很大幫助,本文只是針對最基礎的進行解析,之后有機會繼續解析對他的封裝 前言 雖然一直使用redux+react-redux,但是并沒有真正去講redux最基礎的部分理解透徹,我覺得理解明白redux會對react-redux有一個透徹的理解。 其實,...
閱讀 3371·2023-04-25 14:07
閱讀 3437·2021-09-28 09:35
閱讀 2079·2019-08-30 15:55
閱讀 1396·2019-08-30 13:48
閱讀 2496·2019-08-30 13:16
閱讀 3196·2019-08-30 12:54
閱讀 3231·2019-08-30 11:19
閱讀 1868·2019-08-29 17:17