<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
koa-compose
是一個非常簡單的函數,它接受一箇中介軟體陣列,返回一個函數,這個函數就是一個洋蔥模型的核心。
原始碼地址:github.com/koajs/compo…
網上一搜一大把圖,我就不貼圖了,程式碼也不上,因為等會原始碼就是,這裡只是介紹一下概念。
洋蔥模型是一個非常簡單的概念,它的核心是一個函數,這個函數接受一個函數陣列,返回一個函數,這個函數就是洋蔥模型的核心。
這個返回的函數就是聚合了所有中介軟體的函數,它的執行順序是從外到內,從內到外。
例如:
a
、b
、c
。a
,然後執行b
,最後執行c
。c
後,會從內到外依次執行b
、a
。a
後,返回執行結果。這樣說的可能還是不太清楚,來看一下流程圖:
這裡是兩步操作,第一步是傳入中介軟體陣列,第二步是執行返回的函數,而我們今天要解析就是第一步。
原始碼並不多,只有不到 50 行,我們來看一下:
'use strict' /** * Expose compositor. */ module.exports = compose /** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) } catch (err) { return Promise.reject(err) } } } }
雖然說不到 50 行,其實註釋都快佔了一半,直接看原始碼,先看兩個部分,第一個匯出,第二個是返回的函數。
module.exports = compose function compose (middleware) { // ... return function (context, next) { return dispatch(0) function dispatch (i) { // ... } } }
這裡確實是第一次見這樣玩變數提升的,所以先給大家講一下變數提升的規則:
這裡的module.exports = compose
是變數提升,function compose
是函數提升,所以compose
函數會被提升到module.exports
之前。
下面的return dispatch(0)
是函數內部的變數提升,dispatch
函數會被提升到return
之前。
雖然這樣可行,但是不建議這樣寫,因為這樣寫會讓程式碼變得難以閱讀,不多說了,繼續吧:
function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } // ... }
最開始就是洋蔥模型的要求判斷了,中介軟體必須是陣列,陣列裡面的每一項必須是函數。
繼續看:
return function (context, next) { // last called middleware # let index = -1 return dispatch(0) // ... }
這個函數是返回的函數,這個函數接收兩個引數,context
和next
,context
是上下文,next
是下一個中介軟體,這裡的next
是compose
函數的第二個引數,也就是app.callback()
的第二個引數。
index
註釋寫的很清楚,是最後一個呼叫的中介軟體的索引,這裡初始化為-1
,因為陣列的索引是從0
開始的。
dispatch
函數是用來執行中介軟體的,這裡傳入0
,也就是從第一個中介軟體開始執行。
function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i }
可以看到,dispatch
函數接收一個引數,這個引數是中介軟體的索引,這裡的i
就是dispatch(0)
傳入的0
。
這裡的判斷是為了防止next
被呼叫多次,如果i
小於等於index
,就會丟擲一個錯誤,這裡的index
是-1
,所以這個判斷是不會執行的。
後面就賦值了index
為i
,這樣就可以防止next
被呼叫多次了,繼續看:
let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve()
這裡的fn
是中介軟體,也是當前要執行的中介軟體,通過索引直接從最開始初始化的middleware
陣列裡面取出來。
如果是到了最後一箇中介軟體,這裡的next
指的是下一個中介軟體,也就是app.callback()
的第二個引數。
如果fn
不存在,就返回一個成功的Promise
,表示所有的中介軟體都執行完了。
繼續看:
try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) } catch (err) { return Promise.reject(err) }
這裡就是執行中介軟體的地方了,fn
是剛才取到的中介軟體,直接執行。
然後傳入context
和dispatch.bind(null, i + 1)
,這裡的dispatch.bind(null, i + 1)
就是next
,也就是下一個中介軟體。
這裡就有點遞迴的感覺了,但是並沒有直接呼叫,而是通過外部手動呼叫next
來執行下一個中介軟體。
這裡的try...catch
是為了捕獲中介軟體執行過程中的錯誤,如果有錯誤,就返回一個失敗的Promise
。
老規矩,還是用class
來實現一下這個compose
函數。
class Compose { constructor(middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } this.index = -1 this.middleware = middleware return (next) => { this.next = next return this.dispatch(0) } } dispatch(i) { if (i <= this.index) return Promise.reject(new Error('next() called multiple times')) this.index = i let fn = this.middleware[i] if (i === this.middleware.length) fn = this.next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(this.dispatch.bind(this, i + 1))) } catch (err) { return Promise.reject(err) } } } var middleware = [ (next) => { console.log(1) next() console.log(2) }, (next) => { console.log(3) next() console.log(4) }, (next) => { console.log(5) next() console.log(6) } ] var compose = new Compose(middleware) compose() var middleware = [ (next) => { return next().then((res) => { return res + '1' }) }, (next) => { return next().then((res) => { return res + '2' }) }, (next) => { return next().then((res) => { return res + '3' }) } ] var compose = new Compose(middleware) compose(() => { return Promise.resolve('0') }).then((res) => { console.log(res) })
這次不放執行結果的截圖了,可以直接瀏覽器控制檯中自行執行。
koa-compose
的實現原理就是通過遞回來實現的,每次執行中介軟體的時候,都會返回一個成功的Promise
。
其實這裡不使用Promise
也是可以的,但是使用Promise
可以有效的處理非同步和錯誤。
而且從上面手動實現的程式碼案例中也可以看到,使用Promise
可以有更多的靈活性,寫法也是多元化。
以上就是洋蔥模型 koa-compose原始碼解析的詳細內容,更多關於洋蔥模型 koa-compose的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45