延续和回调有什么区别?

2023-11-29

我一直在浏览整个网络,寻找有关延续的启示,令人难以置信的是,最简单的解释竟然能让像我这样的 JavaScript 程序员完全困惑。当大多数文章用Scheme 中的代码解释延续或使用monad 时尤其如此。

现在我终于认为我已经理解了延续的本质,我想知道我所知道的是否真的是事实。如果我认为正确的东西实际上并不正确,那就是无知,而不是开悟。

所以,这就是我所知道的:

在几乎所有语言中,函数都显式地将值(和控制)返回给调用者。例如:

var sum = add(2, 3);

console.log(sum);

function add(x, y) {
    return x + y;
}

现在,在具有一流函数的语言中,我们可以将控制权和返回值传递给回调,而不是显式返回给调用者:

add(2, 3, function (sum) {
    console.log(sum);
});

function add(x, y, cont) {
    cont(x + y);
}

因此,我们不是从函数返回值,而是继续另一个函数。因此,这个函数被称为第一个函数的延续。

那么延续和回调有什么区别呢?


我认为延续是回调的一个特例。一个函数可以回调任意数量的函数、任意次数。例如:

var array = [1, 2, 3];

forEach(array, function (element, array, index) {
    array[index] = 2 * element;
});

console.log(array);

function forEach(array, callback) {
    var length = array.length;
    for (var i = 0; i < length; i++)
        callback(array[i], array, i);
}

然而,如果一个函数回调另一个函数作为它所做的最后一件事,那么第二个函数被称为第一个函数的延续。例如:

var array = [1, 2, 3];

forEach(array, function (element, array, index) {
    array[index] = 2 * element;
});

console.log(array);

function forEach(array, callback) {
    var length = array.length;

    // This is the last thing forEach does
    // cont is a continuation of forEach
    cont(0);

    function cont(index) {
        if (index < length) {
            callback(array[index], array, index);
            // This is the last thing cont does
            // cont is a continuation of itself
            cont(++index);
        }
    }
}

如果一个函数调用另一个函数作为它所做的最后一件事,那么它被称为尾调用。一些语言(例如Scheme)执行尾调用优化。这意味着尾部调用不会产生函数调用的全部开销。相反,它被实现为一个简单的 goto(调用函数的堆栈帧被尾部调用的堆栈帧替换)。

Bonus:继续延续传球风格。考虑以下程序:

console.log(pythagoras(3, 4));

function pythagoras(x, y) {
    return x * x + y * y;
}

现在,如果每个运算(包括加法、乘法等)都以函数的形式编写,那么我们将得到:

console.log(pythagoras(3, 4));

function pythagoras(x, y) {
    return add(square(x), square(y));
}

function square(x) {
    return multiply(x, x);
}

function multiply(x, y) {
    return x * y;
}

function add(x, y) {
    return x + y;
}

此外,如果我们不允许返回任何值,那么我们将不得不使用延续,如下所示:

pythagoras(3, 4, console.log);

function pythagoras(x, y, cont) {
    square(x, function (x_squared) {
        square(y, function (y_squared) {
            add(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply(x, x, cont);
}

function multiply(x, y, cont) {
    cont(x * y);
}

function add(x, y, cont) {
    cont(x + y);
}

这种不允许返回值(因此必须诉诸于传递延续)的编程风格称为延续传递风格。

然而,连续传递风格存在两个问题:

  1. 传递延续会增加调用堆栈的大小。除非您使用像Scheme 这样消除尾部调用的语言,否则您将面临堆栈空间不足的风险。
  2. 编写嵌套函数是一件痛苦的事。

在 JavaScript 中,通过异步调用延续可以轻松解决第一个问题。通过异步调用延续,函数会在调用延续之前返回。因此,调用堆栈大小不会增加:

Function.prototype.async = async;

pythagoras.async(3, 4, console.log);

function pythagoras(x, y, cont) {
    square.async(x, function (x_squared) {
        square.async(y, function (y_squared) {
            add.async(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply.async(x, x, cont);
}

function multiply(x, y, cont) {
    cont.async(x * y);
}

function add(x, y, cont) {
    cont.async(x + y);
}

function async() {
    setTimeout.bind(null, this, 0).apply(null, arguments);
}

第二个问题通常可以使用名为的函数来解决call-with-current-continuation通常缩写为callcc。很遗憾callcc无法在 JavaScript 中完全实现,但我们可以为其大多数用例编写一个替换函数:

pythagoras(3, 4, console.log);

function pythagoras(x, y, cont) {
    var x_squared = callcc(square.bind(null, x));
    var y_squared = callcc(square.bind(null, y));
    add(x_squared, y_squared, cont);
}

function square(x, cont) {
    multiply(x, x, cont);
}

function multiply(x, y, cont) {
    cont(x * y);
}

function add(x, y, cont) {
    cont(x + y);
}

function callcc(f) {
    var cc = function (x) {
        cc = x;
    };

    f(cc);

    return cc;
}

The callcc函数接受一个函数f并将其应用到current-continuation(缩写为cc). The current-continuation是一个延续函数,它在调用后包装了函数体的其余部分callcc.

考虑函数体pythagoras:

var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);

The current-continuation第二个的callcc is:

function cc(y_squared) {
    add(x_squared, y_squared, cont);
}

同样地current-continuation第一个的callcc is:

function cc(x_squared) {
    var y_squared = callcc(square.bind(null, y));
    add(x_squared, y_squared, cont);
}

自从current-continuation第一个的callcc包含另一个callcc它必须转换为连续传递样式:

function cc(x_squared) {
    square(y, function cc(y_squared) {
        add(x_squared, y_squared, cont);
    });
}

所以本质上callcc从逻辑上将整个函数体转换回我们开始的状态(并为这些匿名函数指定名称cc)。使用 callcc 的这种实现的毕达哥拉斯函数变为:

function pythagoras(x, y, cont) {
    callcc(function(cc) {
        square(x, function (x_squared) {
            square(y, function (y_squared) {
                add(x_squared, y_squared, cont);
            });
        });
    });
}

同样你无法实施callcc在 JavaScript 中,但您可以在 JavaScript 中实现它的连续传递样式,如下所示:

Function.prototype.async = async;

pythagoras.async(3, 4, console.log);

function pythagoras(x, y, cont) {
    callcc.async(square.bind(null, x), function cc(x_squared) {
        callcc.async(square.bind(null, y), function cc(y_squared) {
            add.async(x_squared, y_squared, cont);
        });
    });
}

function square(x, cont) {
    multiply.async(x, x, cont);
}

function multiply(x, y, cont) {
    cont.async(x * y);
}

function add(x, y, cont) {
    cont.async(x + y);
}

function async() {
    setTimeout.bind(null, this, 0).apply(null, arguments);
}

function callcc(f, cc) {
    f.async(cc);
}

功能callcc可用于实现复杂的控制流结构,例如 try-catch 块、协程、生成器、fibers, etc.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

延续和回调有什么区别? 的相关文章

  • CORS 问题。 Flask <-> AngularJS

    使用 angularjs 客户端应用程序和提供 api 的 Flask 应用程序启动一个新项目 我使用 mongodb 作为数据库 我必须立即排除 jsonp 因为我需要能够跨不同端口进行 POST 因此 我们为 Angular 应用程序设
  • 缩短node.js和mongoose中的ObjectId

    我的网址目前如下所示 http www sitename com watch companyId 507f1f77bcf86cd799439011 employeeId 507f191e810c19729de860ea someOtherI
  • MediaRecorder:从多个麦克风录制

    我目前正在使用媒体记录器 https developer mozilla org en US docs Web API MediaRecorder用于在应用程序内录制音频的 API 有没有办法从多个输入设备 例如两个麦克风 进行录音 我可以
  • 如何识别当前打开的每个单独的浏览器窗口?

    如何使用 javascript 唯一地识别当前为所有主要浏览器打开的每个单独的浏览器窗口 让我解释一下我需要了解的内容 并考虑以下场景 我有 3 个当前打开的浏览器窗口 任何现代浏览器 即 Chrome Firefox 等 每个窗口都包含多
  • Typescript:如何在 Redux 中输入 Dispatch

    例如我想删除dispatch any here export const fetchAllAssets gt dispatch any gt dispatch actionGetAllAssets return fetchAll getPr
  • 打开一个新的浏览器窗口/iframe 并在 TEXTAREA 中从 HTML 创建新文档?

    我正在尝试使用 HTML5 的新离线功能编写一个 Web 应用程序 在此应用程序中 我希望能够编辑一些 HTML 完整文档 而不是片段
  • .addClass 仅添加到无序列表中单击的项目符号

    我有一个简短的无序列表 其中有两个项目符号 我添加了一些 Javascript 这样当我单击项目符号时 它会向其中添加一个类 问题是 它将该类添加到所有现有的 li 中 而不仅仅是我单击的那个 这是 JSFiddle http jsfidd
  • Javascript If 语句的语义是什么

    我一直认为 if 语句本质上比较它的论点类似于 true 然而 Firebug 中的以下实验证实了我最担心的事情 在编写 Javascript 15 年之后 我仍然不知道 WTF 发生了什么 gt gt gt true false gt g
  • 使用 javascript/jquery 检查 .css 样式表的名称

    我正在尝试为论坛制作一个小 chrome 扩展 但我只希望它在论坛的某个区域中工作 问题是我不能只做 matches subforum 因为该论坛中的线程无法通过 URL 区分它们所在的子论坛 subforum 有自己的 css 样式表 所
  • 表格中与文本一起内嵌 D3 迷你图

    假设有一个这样的表 var data Orange Orange 6 3 3 2 5 Apple Red 6 2 6 5 5 Grape Purple 9 1 2 3 1 我希望将字符串表示为字符串 但将数字数组表示为 D3 折线图 如果这
  • 将 Blob 设置为 iframe 的“src”

    以下代码在 Chrome 中完美运行 但它不适用于 IE 有人可以告诉我这里出了什么问题吗 iframe src 也设置为 blob 如下所示
  • 获取与请求

    我正在使用 JSON 流并尝试使用 fetch 来使用它 该流每隔几秒发出一些数据 仅当流关闭服务器端时 使用 fetch 来使用流才可以访问数据 例如 var target the url var options method POST
  • 在 Promise 中中止 ajax 请求

    我正在构建一个表单验证并学习承诺 我决定使用承诺模式实现异步验证函数 var validateAjax function value return new Promise function resolve reject ajax data
  • 我想在使用 Jest Snapshots 时获得图像文件名/路径

    我已经开始使用Jest https facebook github io jest 在一个新项目中有很多 我现在正在使用Snapshot https facebook github io jest docs tutorial react h
  • Angular 2.0 路由 - TS 2305 ...没有导出成员“ModulewithProviders”

    我正在关注一个角度2 0教程在 Angular JS 官方上site https angular io docs ts latest tutorial toh pt5 html并在路由练习结束时陷入困境 该代码上次有效 但前几天我点击 np
  • SVG 沿圆弧添加文本

    我正在尝试绘制 SVG 径向饼图 如下所述 色卡 https stackoverflow com a 18210763 1395178 现在我尝试将文本与圆弧一起添加到每个切片 我试图展示Text 1具有与 M 和 A 值完全相同的 x y
  • 使用 Javascript 编辑和保存用户 HTML - 安全性如何?

    例如我有一个Javascript 支持的表单创建工具 您可以使用链接添加元素的 html 块 如输入字段 并使用 TinyMCE 来编辑文本 这些是通过自动保存功能保存的 该功能在特定事件的后台执行 AJAX 调用 被调用的保存函数负责数据
  • Javascript变量是一个对象数组,但无法访问元素

    我正在使用 Firebase 数据库和 Javascript 并且我有代码可以获取每个类别中的每个问题 我有一个名为 类别 的对象 其中包含名称 问题和问题计数 然后它将被推入类别列表 questionsPerCategory 在我刚刚做的
  • 如何使用FileSystem API的window.requestFileSystem?

    我用 JavaScript 编写了以下代码 JavaScript 代码 var fs null function initFS window requestFileSystem window requestFileSystem window
  • 在不调用“then”的情况下解决 Promise

    我有这段代码 它是我为一个名为 Poolio 的 NPM 模块编写的小型 API 的一部分 对于那些支持错误优先回调和承诺的人来说 我遇到的问题似乎是一个常见问题 我们如何在支持两者的同时保持一致的 API 和 API 的一致返回值 例如

随机推荐

  • 如何强制 linq to sql 在生成的 sql 中将 ntext 列转换为 nvarchar(max) ?

    下面的问题让我花了几个小时寻找解决方案 我终于找到了它并想分享 这样我就可以节省其他人花在上面的时间 我在 linq to sql 中有一个查询 需要对类型为 ntext 的列进行排序和分组 对 ntext 列进行排序或分组的原因 text
  • Windows/Python pygame.error:添加 Mp3 文件后视频系统未初始化

    我刚刚在我的 pygame 游戏中添加了一些音乐 但我认为代码太混乱了 没有任何东西在正确的位置 由于添加了此内容 我现在收到此错误 回溯 最近一次调用最后一次 文件 C Users 1234 AppData Local Programs
  • 在 Cortex-M3 CPU 上通过 printf 进行输出调试,在 BKPT 指令处停止 + JTAG 和 sw 端口混乱

    我有一个 Keil ULINK2 USB 仿真器盒连接到JTAG我的主板上的连接器 与板载 Cortex M3 CPU TI Stellaris LuminaryMicro LM3S 系列 配合良好 看起来 JTAG 和 SWJ DP 端口
  • 我在 Heroku 上的 Django 应用程序中添加了 SECRET_KEY 配置变量,但现在它无法在本地工作

    我将我的密钥更改为 Heroku 应用程序上的环境变量 我改变了它 因为我发现将密钥保存在settings py是一个安全风险 但是 现在当我使用时它无法在本地工作python manage py runserver 它给出了有关密钥的错误
  • 为什么在 WIN8 下使用 Touch Injection API 时只能注入一次触摸?

    我正在尝试测试触摸注入 API 以便在使用 Windows 8 时注入多个触摸 为此 我创建 5 个联系人 然后模拟悬停 然后拖动并释放 这对于一键接触来说效果很好 然而 当我再添加 4 个联系人时 没有任何变化 即它产生的结果与只有一个联
  • 用户通知未显示(iOS 10)

    我无法开火UserNotification在 iOS 10 中 我将从我的代码开始 BOOL application UIApplication application didFinishLaunchingWithOptions NSDic
  • 是否可以在 GDB 调试器中“跳转”/“跳过”?

    在 GDB 中调试时是否可以跳转到代码 可执行文件中的某个位置 地址 假设我有类似以下内容的内容 int main caller f1 f1 breakpoint f2 want to skip f2 and jump caller f2
  • 无法在 bash 中执行双括号中的命令

    为了保持一致 我尝试在所有 if 语句中使用双括号 然而 当我要检查我想要运行的命令的返回值时 我确实遇到了问题 在测试了几种创建 if 语句的方法之后 我发现只有没有括号才能执行命令 以下不起作用 if command then echo
  • Rustc/LLVM 为 aarch64 生成错误代码,opt-level=0

    我有两个文件被组装 编译 链接到简约内核中 start s set CPACR EL1 FPEN 0b11 lt lt 20 set BOOT STACK SIZE 8 1024 global boot stack global start
  • geom_bar(aes = ) 和position_dodge(width = ) 中的“width =”以什么单位呈现?

    我想在其中一个躲避的栏上专门放置一些东西 我怎样才能做到这一点 在下面的示例中 我想在条形图的 cat3 组中的 A3 上专门分层一些内容 我知道 cat3 的 x 3 但由于闪避宽度和条宽度的相互依赖性 似乎很难瞄准该条 有没有可以用来计
  • Gradle Java 应用程序的 Proguard 示例

    我是混淆新手 并试图弄清楚如何混淆使用 gradle 创建的 java 应用程序 这个想法是混淆 gradle 构建后创建的可运行 jar 这是 gradle 文件 plugins Apply the java plugin to add
  • 如何在 C# 中连接 WAV 文件

    我有 2 个 wav 文件 我想将它们连接成一个带有两个音轨的文件 是否有任何用于该任务的 API 或 NET 中的一些内置命令 我可以使用它们以某种天才的方式来使该任务成为可能 非常感谢您的帮助 如果我没有记错的话 您可以将第二个文件中的
  • 未来的建造者会在每个设定状态上不断重建

    我正在构建一个使用 api 的应用程序 并且我正在使用 future 构建器来获取数据 但问题是当状态更改时它会重建 我想防止这种情况发生 Thanks 尝试使用这个 class Example extends StatefulWidget
  • 将 XmlHttpRequest 解析为 XmlListModel

    我想将检索到的 xmlHttpRequest 对象放入 XMLListModel 中 我正在使用 qml 主要目标是评估我获得的 xml 并显示列表中的条目 如果有更好的方法 请告诉我 我在这里找到了一个用于分析 xml 的 解决方案 ht
  • Android Studio无法加载项目

    更新最新的Android studio后 我无法再加载项目了 并且错误消息 无法加载项目 com intellij ide plugins PluginManager StartupAbortedException 致命 初始化 com i
  • 将 .XLSX 转换为 Google Sheet 并移动转换后的文件的脚本

    我知道可以使用脚本和驱动 API 将 Excel 文件转换为 Google Sheets 但我正在寻找脚本来转换 Excel 工作表并将转换后的文件移动到其他文件夹 所以需要的步骤如下 将 Excel xls xlsx 从文件夹 A 转换为
  • 即使我运行程序时也获得不同的线程顺序

    有人可以告诉我线程开始执行的顺序吗 我写了下面的代码 class NewThread implements Runnable Thread t NewThread creating a second thread t new Thread
  • 值类中的验证

    SIP 15 意味着可以使用值类别来定义新的数字类别 例如正数 是否可以在没有构造函数的情况下编写底层 gt 0 的约束 而不必调用单独的方法来验证约束 即 创建此类的有效实例是简洁的 如果值类具有构造函数的概念 那么这可能是进行如下验证的
  • Laravel 资源 URL 忽略 https

    我在模板中使用以下代码来加载 CSS 文件 如果我在本地计算机上通过 https 查看页面 则指向 app css 文件的链接也是 https 但是在我的实时服务器上 这种情况不会发生 如果你查看现场直播通过 https 并查看源代码 您可
  • 延续和回调有什么区别?

    我一直在浏览整个网络 寻找有关延续的启示 令人难以置信的是 最简单的解释竟然能让像我这样的 JavaScript 程序员完全困惑 当大多数文章用Scheme 中的代码解释延续或使用monad 时尤其如此 现在我终于认为我已经理解了延续的本质