【问题标题】:Is continuation passing style any different to pipes?延续传递风格与管道有什么不同吗?
【发布时间】:2026-02-17 09:35:01
【问题描述】:

我一直在学习 continuation passing style,尤其是在 javascript 中实现的 asynchronous version,其中一个函数将另一个函数作为最终参数并创建对其的异步调用,将返回值传递给第二个函数。

但是,除了重新创建管道(如在 unix 命令行管道中)或流之外,我看不出延续传递的作用:

replace('somestring','somepattern', filter(str, console.log));

对比

echo 'somestring' | replace 'somepattern' | filter | console.log

除了管道干净得多。使用管道,很明显数据被传递,同时执行被传递给接收程序。事实上,对于管道,我希望数据流能够继续通过管道,而在 CPS 中,我希望是一个串行过程。

可以想象,如果通信对象和更新方法与数据一起传递,而不是完整的切换和返回,CPS 可以扩展到连续管道。

我错过了什么吗? CPS 是否在某些重要方面有所不同(更好?)?

明确地说,我的意思是继续传递,其中一个函数将执行传递给另一个函数,而不仅仅是简单的回调。 CPS 似乎意味着将一个函数的返回值传递给另一个函数,然后退出。

【问题讨论】:

  • “我不太明白延续传递除了重新创建管道之外还有什么作用”:好吧,你首先在 javaScript 中没有管道,所以我会说它 在 JavaScript 中创建 类似管道的用法。这是一些可读性的改进,不幸的是,它在语言中不能像管道一样(至少现在)作为语法糖提供。恕我直言,这根本不是什么。

标签: javascript pipe continuation-passing


【解决方案1】:

UNIX 管道与异步 javascript

unix 管道的行为方式与您链接到的异步 CPS 代码之间存在很大的根本区别。

主要是管道阻塞执行直到整个链完成,而您的异步 CPS 示例将在第一次异步调用完成后立即返回,并且仅在完成时执行您的回调。 (在您的示例中,当超时等待完成时。)

看看这个例子。我将使用 Fetch API 和 Promises 来演示异步行为,而不是 setTimeout 以使其更真实。想象一下,第一个函数f1()负责调用一些webservice并将结果解析为json。这是“管道”到处理结果的f2()

CPS 风格

function f2(json){
    //do some parsing
}

function f1(param, next) {
   return fetch(param).then(response => response.json()).then(json => next(json));
}

// you call it like this:
f1("https://service.url", f2);

如果你把对 f2 的调用从 f1 中移出,你可以写出句法上看起来像管道的东西,但这和上面的完全一样:

function f1(param) {
   return fetch(param).then(response => response.json());
}

// you call it like this:
f1("https://service.url").then(f2);

但这仍然不会阻塞。您不能使用 javascript 中的阻塞机制来执行此任务,根本没有机制可以阻止 Promise。 (在这种情况下,您可以使用同步 XMLHttpRequest,但这不是重点。)

CPS 与管道

以上两种方法的区别在于谁来决定是否调用下一步,具体参数是调用者(后面的例子)还是被调用函数(CPS) .

CPS 非常方便的一个很好的例子是中间件。例如在处理管道中考虑一个缓存中间件。简化示例:

function cachingMiddleware(request, next){
     if(someCache.containsKey(request.url)){
         return someCache[request.url];
     }
     return next(request);
}

中间件执行一些逻辑,检查缓存是否仍然有效:

  • 如果不是,则调用next,然后继续处理管道。

  • 如果有效则返回缓存值,跳过下一次执行。

【讨论】:

    【解决方案2】:

    应用程序级别的继续传递样式

    不是在表达式/功能块级别进行比较,而是在应用程序级别考虑延续传递样式可以通过其“延续”函数(也称为回调函数)提供流控制优势的途径。我们以Express.js 为例:

    每个express middleware 都有一个相当相似的 CPS 函数签名:

     const middleware = (req, res, next) => {
         /* middleware's logic */
         next();
     }
    
     const customErrorHandler = (error, req, res, next) => {
         /* custom error handling logic*/
     };
    

    next是express的原生回调函数。

    更正:next() 函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三个参数。 next() 函数可以任意命名,但按照惯例,它总是命名为“next”

    reqres 分别是 HTTP 请求和 HTTP 响应的命名约定。

    Express.JS 中的路由处理程序将由一个或多个中间件函数组成。 Express.js 会将reqres 对象中的每一个传递给下一个中间件,其中包含前面中间件所做的更改,以及相同的next 回调。

    app.get('/get', middlware1, middlware2, /*...*/ , middlewareN, customErrorHandler)
    

    next 回调函数服务:

    1. 作为中间件的延续

      • 调用next() 会将执行流程传递给下一个中间件函数。在这种情况下,它起到了延续的作用。
    2. 也作为路由拦截器

      • 调用next('Custom error message') 绕过所有后续中间件并将执行控制传递给customErrorHandler 进行错误处理。这使得在路线中间“取消”成为可能!
      • 调用next('route') 绕过后续中间件并将控制权传递给下一个匹配路由,例如。 /get/part.

    在JS中模仿管道

    pipe 有一个 TC39 提案,但在它被接受之前,我们必须手动模仿管道的行为。嵌套 CPS 函数可能会导致回调地狱,所以这是我尝试更简洁的代码:

    假设我们要计算一个句子'The fox jumps over the moon',通过替换起始字符串的一部分(例如props

    const props = "     The [ANIMAL] [ACTION] over the [OBJECT] "
    

    每个替换字符串不同部分的函数都用一个数组排序

    const insertFox = s => s.replace(/\[ANIMAL\]/g, 'fox')
    const insertJump = s => s.replace(/\[ACTION\]/g, 'jumps')
    const insertMoon = s => s.replace(/\[OBJECT\]/g, 'moon')
    const trim = s => s.trim()
    const modifiers = [insertFox, insertJump, insertMoon, trim]
    

    我们可以使用reduce 实现同步的、非流式传输的管道行为。

    const pipeJS = (chain, callBack) => seed => 
        callBack(chain.reduce((acc, next) => next(acc), seed))
    const callback = o => console.log(o)
    
    pipeJS(modifiers, callback)(props) //-> 'The fox jumps over the moon'
    

    这里是pipeJS的异步版本;

    const pipeJSAsync = chain => async seed =>
        await chain.reduce((acc, next) => next(acc), seed)
    const callbackAsync = o => console.log(o)
    
    pipeJSAsync(modifiers)(props).then(callbackAsync) //-> 'The fox jumps over the moon'
    

    希望这会有所帮助!

    【讨论】: