前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >异步处理的脑力游戏

异步处理的脑力游戏

作者头像
tyrchen
发布2018-03-28 15:55:46
7390
发布2018-03-28 15:55:46
举报
文章被收录于专栏:程序人生程序人生

用过 node.js 的同学都知道,它实现了 Observer 设计模式,做了一套类似于 Python 的 event listener,叫 EventEmitter。你可以创建(或者扩展)一个 EventEmitter,在需要触发事件的时候,emit 你想要的事件,然后在其他的代码中,监听这个事件进行处理。

EventEmitter 在做 library 或者 framework 的时候尤其有用,因为它将事件和事件的处理解耦,事件的触发者不必关心事件是由谁来处理,怎么处理的。然而,和 Python 这种顺序执行的语言不同,node.js 是完全异步的,这就导致了事件的处理可能并非按照你想象的方式来进行。最近我在用 nodejs 做一个 API 框架,就遇到了这样的坎。

问题是这样的:我希望我的框架足够灵活使得在处理某个路由的前后,可以由框架的用户注入他们想要执行的代码(middleware 并不能完全满足这种需求),比如说:

这样,框架的使用者可以:

然而,理想很丰满,现实很骨感。这个实现有这样几个问题:

  1. 如果 preprocessing 的 listener 是一个异步处理的函数,preprocessor() 会晚于 r.action() 执行完毕,这并不是我们所希望的!
  2. 如果 preprocessing 有多个 listener(作为框架,需要保证这种灵活性),谁来调用 next() 把控制权交给 r.action()?
  3. 如果 preprocessor() 抛出异常,我们怎么终止 r.ation() 的执行?
  4. 如果 preprocessing 有多个 listener,我们怎么保证它们的执行顺序?

这些问题处理起来很棘手,很难找到一个简单的解决方案。callback 显然是不对路的,这会让 listener 的代码非常丑陋。我们知道,在 javascript 里处理异步的一个很漂亮的解决方案是 Promise,那么我们就用 Promise 来尝试一下:

如果 emit 能够返回一个 Promise,那么我们就可以解决前三个问题。我们收回 next() 执行的控制权。emit() 时不把 next() 传递给 listener,而是在 listeners 执行完毕后,返回的 promise 里进行 resolve / reject 时再去执行。这样,preprocessor() 执行完才会执行 next() 或者 next(err),把控制权交给 r.action() 或者错误处理流程。

然而,正常 EventEmitter 在做 emit() 时,仅仅返回一个 boolean,它并不会返回一个 Promise。这难不倒我们,只需要做个 monkey patch,我们就可以让 EventEmitter 支持这个功能。

monkey patch 在 javascript 里面很简单,大家也很可能做过这样的事情:

这个其实就是 monkey patch,通过给 Arrray.prototype 注入新的函数,我们改变了 Array 的行为。同样的,我们只需要为 EventEmitter 注入新的函数,使其返回 Promise,就可以满足我们的需求了。注意,做 monkey patch 时不要改变已有的函数,这样会引发很多问题,所以我们应该建一个新的函数:

在这个函数里,我们可以执行所有的 event handler。由于多个 event handler 可能包含同步的函数,也可能包含异步的函数,我们需要将其统一。同步执行是异步执行的一个特列,所以我们可以把同步执行的结果转换成 Promise,类似这样:

这样,event handler 的执行结果,即便抛出异常,都被我们异步化封装成一个 Promise。当然,作为框架本身,我们是不知道 listener 是同步函数还是异步函数,所以我们要求一个 listener,如果是异步处理,那么必须返回 Promise。

如果我们有多个 event listeners,那么可以用 Promise.All() 来把所有异步事件聚合在一起。

大体上,我们已经完成了想要的功能,只剩下第4个问题:如果 preprocessing 有多个 listener,我们怎么保证它们的执行顺序?Promise.All() 会并行执行所有的 listeners,这很有用,但很多时候,我们也许希望 listener 不管是异步还是同步,都能够按顺序一个一个执行。

如果你知道 Observable,那么这个问题可以很轻松地使用 Observable 解决。你只需要为每个 handler 的执行创建一个 Observable 对象,然后 使用 concatAll() 将其顺序执行。

当我们需要顺序执行时,我们使用 concatAll(),当我们需要并行执行时,我们可以使用 flatMap()(map() + mergeAll() 等价于 flatMap()。

至此,我们的问题全部解决,我们可以 monkey patch 出一个 emitAsync(),用于异步(包括同步)的 listeners 的并行处理;同时也可以 monkey patch 出一个 emitAsyncSeq() 用于异步(包括同步)的 listeners 的顺序执行。

感兴趣的朋友可以移步:https://github.com/tyrchen/node-eventasync ,看看完整的实现。如果你想在你的项目代码里使用,可以直接:

$ npm install eventasync

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档