50行代码串行Promise,koa洋葱模型原来这么有趣?

发表于 3年以前  | 总阅读数:351 次

1 . 前言

之前写的[《学习源码整体架构系列》]包含jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4十余篇源码文章。其中最新的两篇是:

[Vue 3.2 发布了,那尤雨溪是怎么发布 Vue.js 的?] [初学者也能看懂的 Vue3 源码中那些实用的基础工具函数] 写相对很难的源码,耗费了自己的时间和精力,也没收获多少阅读点赞,其实是一件挺受打击的事情。从阅读量和读者受益方面来看,不能促进作者持续输出文章。

所以转变思路,写一些相对通俗易懂的文章。其实源码也不是想象的那么难,至少有很多看得懂

之前写过 koa 源码文章[学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理] 比较长,读者朋友大概率看不完,所以本文从koa-compose50行源码讲述。

本文涉及到的 koa-compose 仓库[1] 文件,整个index.js文件代码行数虽然不到 50 行,而且测试用例test/test.js文件 300 余行,但非常值得我们学习。

歌德曾说:读一本好书,就是在和高尚的人谈话。同理可得:读源码,也算是和作者的一种学习交流的方式。

阅读本文,你将学到:

1. 熟悉 koa-compose 中间件源码、可以应对面试官相关问题
2. 学会使用测试用例调试源码
3. 学会 jest 部分用法

2 . 环境准备

2.1 克隆 koa-compose 项目

本文仓库地址 koa-compose-analysis[2],求个star~

# 可以直接克隆我的仓库,我的仓库保留的 compose 仓库的 git 记录
git clone https://github.com/lxchuan12/koa-compose-analysis.git
cd koa-compose/compose
npm i

顺带说下:我是怎么保留 compose 仓库的 git 记录的。

# 在 github 上新建一个仓库 `koa-compose-analysis` 克隆下来
git clone https://github.com/lxchuan12/koa-compose-analysis.git
cd koa-compose-analysis
git subtree add --prefix=compose https://github.com/koajs/compose.git main
# 这样就把 compose 文件夹克隆到自己的 git 仓库了。且保留的 git 记录

关于更多 git subtree,可以看这篇文章用 Git Subtree 在多个 Git 项目间双向同步子项目,附简明使用手册[3]

接着我们来看怎么根据开源项目中提供的测试用例调试源码。

2.2 根据测试用例调试 compose 源码

VSCode(我的版本是 1.60 )打开项目,找到 compose/package.json,找到 scriptstest 命令。

// compose/package.json
{
    "name": "koa-compose",
    // debug (调试)
    "scripts": {
        "eslint": "standard --fix .",
        "test": "jest"
    },
}

scripts上方应该会有debug或者调试字样。点击debug(调试),选择 test

VSCode 调试

接着会执行测试用例test/test.js文件。终端输出如下图所示。

koa-compose 测试用例输出结果

接着我们调试 compose/test/test.js 文件。我们可以在 45行 打上断点,重新点击 package.json => srcipts => test 进入调试模式。如下图所示。

koa-compose 调试

接着按上方的按钮,继续调试。在compose/index.js文件中关键的地方打上断点,调试学习源码事半功倍。

更多 nodejs 调试相关 可以查看官方文档[4]

顺便提一下几个调试相关按钮。

  1. 继续(F5)

  2. 单步跳过(F10)

  3. 单步调试(F11)

  4. 单步跳出(Shift + F11)

  5. 重启(Ctrl + Shift + F5)

  6. 断开链接(Shift + F5)

接下来,我们跟着测试用例学源码。

3 . 跟着测试用例学源码

分享一个测试用例小技巧:我们可以在测试用例处加上only修饰。

// 例如
it.only('should work', async () => {})

这样我们就可以只执行当前的测试用例,不关心其他的,不会干扰调试。

3.1 正常流程

打开 compose/test/test.js 文件,看第一个测试用例。

// compose/test/test.js
'use strict'

/* eslint-env jest */

const compose = require('..')
const assert = require('assert')

function wait (ms) {
  return new Promise((resolve) => setTimeout(resolve, ms || 1))
}
// 分组
describe('Koa Compose', function () {
  it.only('should work', async () => {
    const arr = []
    const stack = []

    stack.push(async (context, next) => {
      arr.push(1)
      await wait(1)
      await next()
      await wait(1)
      arr.push(6)
    })

    stack.push(async (context, next) => {
      arr.push(2)
      await wait(1)
      await next()
      await wait(1)
      arr.push(5)
    })

    stack.push(async (context, next) => {
      arr.push(3)
      await wait(1)
      await next()
      await wait(1)
      arr.push(4)
    })

    await compose(stack)({})
    // 最后输出数组是 [1,2,3,4,5,6]
    expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
  })
}

大概看完这段测试用例,context是什么,next又是什么。

koa的文档[5]上有个非常代表性的中间件 gif 图。

中间件 gif 图

compose函数作用就是把添加进中间件数组的函数按照上面 gif 图的顺序执行。

3.1.1 compose 函数

简单来说,compose 函数主要做了两件事情。

  • 1 . 接收一个参数,校验参数是数组,且校验数组中的每一项是函数。

  • 2 . 返回一个函数,这个函数接收两个参数,分别是contextnext,这个函数最后返回Promise

/**
 * 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){
      // 省略,下文讲述
    }
  }
}

接着我们来看 dispatch 函数。

3.1.2 dispatch 函数

function dispatch (i) {
  // 一个函数中多次调用报错
  // await next()
  // await next()
  if (i <= index) return Promise.reject(new Error('next() called multiple times'))
  index = i
  // 取出数组里的 fn1, fn2, fn3...
  let fn = middleware[i]
  // 最后 相等,next 为 undefined
  if (i === middleware.length) fn = next
  // 直接返回 Promise.resolve()
  if (!fn) return Promise.resolve()
  try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
  } catch (err) {
    return Promise.reject(err)
  }
}

值得一提的是:bind函数是返回一个新的函数。第一个参数是函数里的this指向(如果函数不需要使用this,一般会写成null)。这句fn(context, dispatch.bind(null, i + 1)i + 1 是为了 let fn = middleware[i]middleware中的下一个函数。也就是 next 是下一个中间件里的函数。也就能解释上文中的 gif图函数执行顺序。测试用例中数组的最终顺序是[1,2,3,4,5,6]

3.1.3 简化 compose 便于理解

自己动手调试之后,你会发现 compose 执行后就是类似这样的结构(省略 try catch 判断)。

// 这样就可能更好理解了。
// simpleKoaCompose
const [fn1, fn2, fn3] = stack;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};

也就是说koa-compose返回的是一个Promise,从中间件(传入的数组)中取出第一个函数,传入context和第一个next函数来执行。 第一个next函数里也是返回的是一个Promise,从中间件(传入的数组)中取出第二个函数,传入context和第二个next函数来执行。 第二个next函数里也是返回的是一个Promise,从中间件(传入的数组)中取出第三个函数,传入context和第三个next函数来执行。 第三个... 以此类推。最后一个中间件中有调用next函数,则返回Promise.resolve。如果没有,则不执行next函数。这样就把所有中间件串联起来了。这也就是我们常说的洋葱模型。

洋葱模型图如下图所示:

不得不说非常惊艳,“玩还是大神会玩”

3.2 错误捕获

it('should catch downstream errors', async () => {
  const arr = []
  const stack = []

  stack.push(async (ctx, next) => {
    arr.push(1)
    try {
      arr.push(6)
      await next()
      arr.push(7)
    } catch (err) {
      arr.push(2)
    }
    arr.push(3)
  })

  stack.push(async (ctx, next) => {
    arr.push(4)
    throw new Error()
  })

  await compose(stack)({})
  // 输出顺序 是 [ 1, 6, 4, 2, 3 ]
  expect(arr).toEqual([1, 6, 4, 2, 3])
})

相信理解了第一个测试用例和 compose 函数,也是比较好理解这个测试用例了。这一部分其实就是对应的代码在这里。

try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
  return Promise.reject(err)
}

3.3 next 函数不能调用多次

it('should throw if next() is called multiple times', () => {
  return compose([
    async (ctx, next) => {
      await next()
      await next()
    }
  ])({}).then(() => {
    throw new Error('boom')
  }, (err) => {
    assert(/multiple times/.test(err.message))
  })
})

这一块对应的则是:

index = -1
dispatch(0)
function dispatch (i) {
  if (i <= index) return Promise.reject(new Error('next() called multiple times'))
  index = i
}

调用两次后 iindex 都为 1,所以会报错。

compose/test/test.js文件中总共 300余行,还有很多测试用例可以按照文中方法自行调试。

4 . 总结

虽然koa-compose源码 50行 不到,但如果是第一次看源码调试源码,还是会有难度的。其中混杂着高阶函数、闭包、Promisebind等基础知识。

通过本文,我们熟悉了 koa-compose 中间件常说的洋葱模型,学会了部分 jest[6] 用法,同时也学会了如何使用现成的测试用例去调试源码。

相信学会了通过测试用例调试源码后,会觉得源码也没有想象中的那么难

开源项目,一般都会有很全面的测试用例。除了可以给我们学习源码调试源码带来方便的同时,也可以给我们带来的启发:自己工作中的项目,也可以逐步引入测试工具,比如 jest

此外,读开源项目源码是我们学习业界大牛设计思想和源码实现等比较好的方式。

看完本文,非常希望能自己动手实践调试源码去学习,容易吸收消化。另外,如果你有余力,可以继续看我的 koa-compose 源码文章:[学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理]

参考资料

[1]koa-compose 仓库: https://github.com/koajs/compose

[2]本文仓库地址 koa-compose-analysis: https://github.com/lxchuan12/koa-compose-analysis.git

[3]用 Git Subtree 在多个 Git 项目间双向同步子项目,附简明使用手册: https://segmentfault.com/a/1190000003969060

[4]更多 nodejs 调试相关 可以查看官方文档: https://code.visualstudio.com/docs/nodejs/nodejs-debugging

[5]koa的文档: https://github.com/koajs/koa/blob/master/docs/guide.md#writing-middleware

[6]jest: https://github.com/facebook/jest

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/n_7oYLEeApoeK5SD9hVi-Q

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录