RQ
Douglas Crockford
2015-10-06
翻译 http://blog.csdn.net/zhangxin09
RQ 是一个运行在服务端用于管理异步的小型 JavaScript 库。
RQ
is a small JavaScript library for managing asynchronicity in server applications.
源码在 https://github.com/douglascrockford/RQ。本页在 http://www.RQ.crockford.com/。采用公有领域。
The source is available at https://github.com/douglascrockford/RQ. This page is available at http://www.RQ.crockford.com/. It is in the Public Domain.
异步 Asynchronicity
无论用户界面还是服务端,异步渐成为解决繁复问题的一种良好手段。异步函数会尽快地把控制权交到调用者手中。调用者通常会有片刻的等待,才会晓得通讯成功或失败。通讯过程可能会使用到回调函数或者延续(continuation)。
Asynchronicity is becoming the preferred method for solving a large class of problems, from user interfaces to servers. Asynchronous functions return control to the caller almost immediately. Success or failure will be communicated somehow in the future, but usually the caller will resume long before that occurs. The communication will probably make use of some sort of callback function or continuation.
最简单来说,旧方式写法
At its simplest, it means that instead of writing
function oldway( . . . ) { . . . return result; }
将会改为现在的,
we now sometimes write
function newway(callback, . . . ) { . . . return callback(result); }
然而,这不是一句话两句话就能说完的。
But of course it is not that simple.
服务端所进行的工作流,往往与前端的有极大不同。一次请求可能会触发多个进程,当中完成了一个进程后就把交给下一个进程。也就是说,每个步骤可能依靠其他进程来处理,所以必须依靠回调来返回结果。等待的同时不会阻塞程序。原始的方法是,在前一个的回调函数中调用下一步。但通常这种程序写起来比较麻烦,难以阅读,也难以维护。
Servers offer workflows that are significantly different than those found in user interfaces. An incoming message might require several processing steps in which the result of each step is given to the next step. Since each step may be concluded in a different processing turn, callbacks must be used. The program may not block while awaiting results. The naïve approach is start each step in the callback function of the previous step. This is a very awkward way to write, producing programs that are brittle, difficult to read, and difficult to maintain.
一个流程可以分为若干独立的步骤,意味着不但可以并行运行这些步骤,而且更重要的结果是会快很多。整体所消耗的时间仅仅是最满那次步骤之时间,非所有步骤所累加的时间——显然那会快很多。但是如果用简单的回调则不容易发挥并行的优点。原始的方法是,连续地执行每个步骤。但通常这种程序写起来比较麻烦,难以阅读,也难以维护。
A workflow might have several independent steps, which means that those steps could be taken in parallel, which could have significant performance benefits. The elapsed time of the steps could be the slowest of all of the steps, instead of the total of all of the steps, which could be a dramatic speed up. But it is not obvious how to take advantage of parallelism with simple callbacks. The naïve approach is to perform each step serially. This is a very awkward way to write, producing programs that a brittle, difficult to read, difficult to maintain, and much too slow.
该并行模式是如此的困难以至于某些用户吐槽异步这玩意搞起来不大自然(或曰“反人类”)。但要搞清楚的是这不是异步本身的问题(异步其实是极好的),只不过我们没有适当的工具来异步罢了。尽管已经有了诸如 promise 等的工具来搞定那些问题,但 promise 本身却不是为服务端而设计的。
This pattern is so problematic that some of its users have denounced asynchronicity, declaring that it is unnatural and impossible to manage. But it turns out that the problem isn't with asynchronicity. The problem is trying to do asynchronicity without proper tools. There are lots of tools available now, including promises. There are many good things that can be done with promises, but promises were not designed to help manage workflows in servers.
于是,发明 RQ 就有了合理的解释了。异步是好东西。我们不应该无视或拒绝它。我们应该尽量接受它,因为它是大势所趋。而 RQ 就赋予了你这么一个简单的工具实现异步。
That is specifically what RQ
was designed to do. Asynchronicity is our friend. We should not attempt to hide it or deny it. We must embrace asynchronicity because it is our destiny. RQ
gives you the simple tools you need to do that.
工作流 Workflow
RQ 中的一个完整工作流可以分解成为若干的步骤(或称作任务或工作)。每个步骤视为函数。这些函数又可以称为“请求者(requestors)”, 因为调用中很可能会发起请求。RQ 的作用在于统筹这些请求者们,采用串行或并行的方式来处理它们。
With RQ
, a workflow is broken into a set of steps or tasks or jobs. Each step is represented by a function. These functions are called requestors because calling one is likely to initiate a request. RQ
provides services that can collect some requestors together and process them sequentially or in parallel.
举个例子,respond 请求者首先会调用 getId,得到结果后把结果交给 getPreference 请求者,然后又把得到的结果交割 getStuff 请求者。最后一个 请求者 buildPage 会把最后一个结果 stuff 用于构建页面。
For example, the respond
requestor will call the getId
requestor, give that result to the getPreference
requestor, and give that result to the getStuff
requestor. The buildPage
requestor will use the stuff to build a page.
respond = RQ.sequence([ getId, getPreference, getStuff, buildPage, ]);
上述使用的 getStuff 请求者会返回 stuff 数组,所谓 stuff 数组里面的内容又是并行方式获取的。发出的请求有 getNav、
getAds、
getWeather
和 getMessageOfTheDay
这一组,并也同时请求 getHoroscope
和 getGossip
这一组。但是这两组它们有什么不同呢?后一组被视为不要紧的,可有可无的:如果前一组搞定的话而后一组未搞定的话,则不管后一组怎么样不会等待,就视为全部搞定。反过来说,如果后一组在前一组成功的时限内,那么后一组将会有效。
The getStuff
requestor that was used above will produce an array of stuff, and it can get all of the stuff in parallel. It will begin the getNav
, getAds
, getWeather
, and getMessageOfTheDay
jobs, and also begin getHoroscope
and getGossip
. Those last two are considered unimportant. If they can be finished before the four main jobs finish, then they will be included in the result. But we won't wait for them.
getStuff = RQ.parallel([ getNav, getAds, getWeather, getMessageOfTheDay ], [ getHoroscope, getGossip ]);
RQ
提供竞赛机制的支持。所谓竞赛,表示一组步骤中,以最快完成的那个为胜利者,取胜利者的结果,其余的不理。我们上述使用的 getAds 请求者实际是这样的。
RQ
can also support races, where several jobs are started at once and the first one to finish successfully wins. We could have created the getAds
requestor that was used above like this:
getAds = RQ.race([ getAd(adnet.klikHaus), getAd(adnet.inUFace), getAd(adnet.trackPipe) ]);
getAd
是生产请求者的工厂函数。它有个有一个表示最快广告网络的参数。在这个例子中会发起对这三个广告网络的请求,看哪个最快响应的便是结果。
getAd
is a factory function that makes requestors. getAd
takes a parameter that identifies an advertising network. In this example, requests will be made of three advertising networks. The first to provide an acceptable response will win.
RQ 亦支持“备胎(fallback)机制”。倘若 A 计划失败则尝试 B 计划;B 计划失败的话,则 C 计划上。上述所用的 getWeather 请求者即是一个“备胎函数”。首先尝试从本地缓存获取结果;倘若失败则从本地数据库获取;最后也不行的话,则尝试从远程数据库获取天气。
RQ
can also support fallbacks. If Plan A fails, try Plan B. And if that fails, Plan C. The getWeather
requestor that was used above could have been made as a fallback. It would first try to get a result from the local cache. If that fails, it will try the local database. If that fails, it will try the remote database.
getWeather = RQ.fallback([ fetch("weather", localCache), fetch("weather", localDB), fetch("weather", remoteDB) ]);
小结下,RQ
有四种函数:串行函数 RQ.sequence
、并行函数 RQ.parallel
、 竞争函数 RQ.race
和备胎函数 RQ.fallback
。每种函数都可以传入请求者组成的数组,然后返回一个总的请求者。这个所谓的总的请求者就是企图把多个请求者函数合并为一个。每个函数可传入超时函数,一旦超时了则取消任务。
RQ
provides just four functions: RQ.sequence
, RQ.parallel
, RQ.race
, and RQ.fallback
. Each takes an array of requestors and returns a requestor that combines them into a unit. Each can also take an optional time limit which can cancel the jobs and produce an early failure if time runs out.