异步顺序调用方法

2024-04-07

我有一个在方法中调用的方法列表,如下所示:

this.doOneThing();
someOtherObject.doASecondThing();
this.doSomethingElse();

当这是同步时,它们会一个接一个地执行,这是必需的。但现在我有 someOtherObject.doASecondThing() 作为异步,我也可能将 doOneThing 变成异步。我可以使用回调并从回调内部调用 that.doSomethingElse:

var that = this;
this.doOneThing( function () { 
                    someOtherObject.doASecondThing(function () {
                        that.doSomethingElse();
                    });
                  });

但是,由于序列不断增长,回调相互调用似乎有点混乱,由于某种原因,它使序列看起来不像以前那么明显,并且缩进可能会随着序列中调用的方法数量而增长。

有没有办法让这个看起来更好?我也可以使用观察者模式,但在我看来,它也不会让事情变得非常明显。

Thanks,


延续,以及为什么它们会导致回调意大利面

在回调中编写会迫使您有时采用类似于“连续传递风格”(CPS)的方式进行编写,这是一种非常强大但困难的技术。它代表了控制的完全反转,实际上是“颠倒”了计算。 CPS 使代码的结构明确地反映程序的控制流(有时是好事,有时是坏事)。实际上,您显式地写下了匿名函数的堆栈。

作为理解此答案的先决条件,您可能会发现这很有用:

http://matt.might.net/articles/by-example-continuation-passing-style/ http://matt.might.net/articles/by-example-continuation-passing-style/

例如,这就是您正在做的事情:

function thrice(x, ret) {
    ret(x*3)
}
function twice(y, ret) {
    ret(y*2)
}
function plus(x,y, ret) {
    ret(x+y)
}

function threeXPlusTwoY(x,y, ret) {
    // STEP#1
    thrice(x,                 // Take the result of thrice(x)...
        function(r1) {        // ...and call that r1.
            // STEP#2
            twice(y,            // Take the result of twice(y)...
                function(r2) {  // ...and call that r2.
                    // STEP#3
                    plus(r1,r2,   // Take r1+r2...
                        ret       // ...then do what we were going to do.
                    )
                }
            )
        }
    )
}

threeXPlusTwoY(5,1, alert);  //17

正如您所抱怨的,这会导致代码相当缩进,因为闭包是捕获此堆栈的自然方法。


Monad 来救援

取消缩进 CPS 的一种方法是像 Haskell 中那样“单子”编写。我们该怎么做呢?在 javascript 中实现 monad 的一种好方法是使用点链表示法,类似于 jQuery。 (看http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/ http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/一个有趣的消遣。)或者我们可以使用反射。

但首先我们需要一种方法来“写下管道”,然后我们可以找到一种方法将其抽象出来。不幸的是,用 javascript 编写通用的 monad 语法有点困难,所以我将使用列表来表示计算。

 

// switching this up a bit:
// it's now 3x+2x so we have a diamond-shaped dependency graph

// OUR NEW CODE
var _x = 0;
var steps = [
    [0,  function(ret){ret(5)},[]],  //step0:
    [1,  thrice,[_x]],               //step1: thrice(x)
    [2,  twice,[_x]],                //step2: twice(x)
    [3,  plus,[1, 2]]                //step3: steps[1]+steps[2] *
]
threeXPlusTwoX = generateComputation(steps);

//*this may be left ambiguous, but in this case we will choose steps1 then step2
// via the order in the array

这有点难看。但我们可以使这个未缩进的“代码”起作用。我们可以稍后再考虑让它变得更漂亮(在最后一节)。在这里我们的目的是写下所有“必要的信息”。我们想要一种简单的方法来编写每一“行”,以及我们可以编写它们的上下文。

现在我们实现一个generateComputation它会生成一些嵌套的匿名函数,如果我们执行它,这些函数将按顺序执行上述步骤。这是这样的实现:

function generateComputation(steps) {
    /*
    * Convert {{steps}} object into a function(ret), 
    * which when called will perform the steps in order.
    * This function will call ret(_) on the results of the last step.
    */
    function computation(ret) {
        var stepResults = [];

        var nestedFunctions = steps.reduceRight(
            function(laterFuture, step) {
                var i            = step[0];  // e.g. step #3
                var stepFunction = step[1];  // e.g. func: plus
                var stepArgs     = step[2];  // e.g. args: 1,2

                console.log(i, laterFuture);
                return function(returned) {
                    if (i>0)
                        stepResults.push(returned);
                    var evalledStepArgs = stepArgs.map(function(s){return stepResults[s]});
                    console.log({i:i, returned:returned, stepResults:stepResults, evalledStepArgs:evalledStepArgs, stepFunction:stepFunction});
                    stepFunction.apply(this, evalledStepArgs.concat(laterFuture));
                }
            },
            ret
        );

        nestedFunctions();
    }
    return computation;
}

示范:

threeXPlusTwoX = generateComputation(steps)(alert);  // alerts 25

边注:reduceRight语义意味着右侧的步骤将更深入地嵌套在函数中(将来会更深入)。供不熟悉的人参考,[1,2,3].reduce(f(_,_), x) --> f(f(f(0,1), 2), 3), and reduceRight(由于设计考虑不佳)实际上相当于[1.2.3].reversed().reduce(...)

Above, generateComputation制作了一堆嵌套函数,在准备过程中将它们彼此包装在一起,并在使用时进行评估...(alert),将它们一一剥开,输入到计算中。

旁注:我们必须使用 hack,因为在前面的示例中,我们使用闭包和变量名来实现 CPS。 Javascript 不允许足够的反射来做到这一点,而不求助于制作字符串和eval所以我们暂时避开函数式风格,选择改变一个跟踪所有参数的对象。因此,上面的内容更接近地复制了以下内容:

var x = 5;
function _x(ret) {
    ret(x);
}

function thrice(x, ret) {
    ret(x*3)
}
function twice(y, ret) {
    ret(y*2)
}
function plus(x,y, ret) {
    ret(x+y)
}

function threeXPlusTwoY(x,y, ret) {
    results = []
    _x(
        return function(x) {
            results[0] = x;

            thrice(x,                 // Take the result of thrice(x)...
                function(r1) {        // ...and call that r1.
                    results[1] = r1;

                    twice(y,            // Take the result of twice(y)...
                        function(r2) {  // ...and call that r2.
                            results[2] = r2;

                            plus(results[1],results[2],   // Take r1+r2...
                                ret       // ...then do what we were going to do.
                            )
                        }
                    )
                }
            )

        }
    )
}

理想语法

但我们仍然希望以合理的方式编写函数。我们将如何ideally喜欢编写我们的代码来利用 CPS,但同时保持我们的理智?文献中有很多观点(例如,Scala 的shift and reset运算符只是实现此目的的众多方法之一),但为了理智起见,让我们找到一种为常规 CPS 制作语法糖的方法。有一些可能的方法可以做到这一点:

 

// "bad"
var _x = 0;
var steps = [
    [0,  function(ret){ret(5)},[]],  //step0:
    [1,  thrice,[_x]],               //step1: thrice(x)
    [2,  twice,[_x]],                //step2: twice(x)
    [3,  plus,[1, 2]]                //step3: steps[1]+steps[2] *
]
threeXPlusTwoX = generateComputation(steps);

...变成...

  • 如果回调位于一个链中,我们可以轻松地将一个回调传递给下一个回调,而不必担心命名。这些函数只有一个参数:回调参数。 (如果没有,您可以在最后一行对函数进行柯里化,如下所示。)这里我们可以使用 jQuery 风格的点链。

 

// SYNTAX WITH A SIMPLE CHAIN
// ((2*X) + 2)
twiceXPlusTwo = callbackChain()
    .then(prompt)
    .then(twice)
    .then(function(returned){return plus(returned,2)});  //curried

twiceXPlusTwo(alert);
  • 如果回调形成依赖树,我们也可以摆脱 jQuery 风格的点链,但这将违背为 CPS 创建单子语法的目的,即扁平化嵌套函数。因此我们在这里不做详细介绍。

  • 如果回调形成依赖非循环图(例如,2*x+3*x,其中 x 使用了两次)我们需要一种方法命名中间结果一些回调。这就是有趣的地方。我们的目标是尝试模仿语法http://en.wikibooks.org/wiki/Haskell/Continuation_passing_style http://en.wikibooks.org/wiki/Haskell/Continuation_passing_style以其do-将函数“展开”和“重新包装”进出 CPS 的符号。不幸的是[1, thrice,[_x]]语法是我们可以轻松达到的最接近的语法(甚至不是很接近)。您可以用另一种语言进行编码并编译为 JavaScript,或者使用 eval(对不祥的音乐进行排队)。有点矫枉过正了。替代方案必须使用字符串,例如:

 

// SUPER-NICE SYNTAX
// (3X + 2X)
thriceXPlusTwiceX = CPS({
    leftPart: thrice('x'),
    rightPart: twice('x'),
    result: plus('leftPart', 'rightPart')
})

您只需对generateComputation我描述了。首先,您将其调整为使用逻辑名称('leftPart'等)而不是数字。然后你让你的函数实际上是惰性对象,其行为如下:

thrice(x).toListForm() == [<real thrice function>, ['x']]
or
thrice(x).toCPS()(5, alert)  // alerts 15
or
thrice.toNonCPS()(5) == 15

(您可以使用某种装饰器以自动方式完成此操作,而不是手动完成。)

旁注:所有回调函数都应遵循有关回调参数位置的相同协议。例如,如果您的函数以myFunction(callback, arg0, arg1, ...) or myFunction(arg0, arg1, ..., callback)它们可能不太兼容,但如果它们不兼容,您可能可以执行 javascript 反射 hack 来查看函数的源代码并将其正则表达式出来,因此不必担心它。

为什么要经历这么多麻烦?这可以让你混入setTimeouts and prompts 和 ajax 请求,而不会遭受“缩进地狱”的困扰。您还可以获得一大堆其他好处(例如能够编写 10 行非确定性搜索数独求解器,以及实现任意控制流运算符),我在此不再赘述。

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

异步顺序调用方法 的相关文章

  • Three.js:缩放几何图形后错误的 BoundingBox

    在我的场景中 我有一个简单的立方体 var test new THREE Mesh new THREE CubeGeometry 10 10 10 new THREE MeshBasicMaterial scene add test 该立方
  • 如何在react-bootstrap中禁用表单提交的

    在下面的代码片段中 我有许多文本类型的输入表单 如果用户点击 我似乎会得到相同的合成事件 就像他们按下提交按钮一样 我想忽略作为表单提交 只允许一个人按下 提交 按钮 我删除了一些表单组以减少示例 在所有情况下 按钮或 ENTER 键 e
  • 如何将内联 JavaScript 与 Express/Node.js 中动态生成的内容分开?

    对于具有几年 Web 开发经验但没有找到答案的人来说 这是一个有点菜鸟的问题程序员堆栈交换 or Google 我决定在这里问一下 我在用Express网络框架Node js 但这个问题并不特定于任何 Web 框架或编程语言 以下是从数据库
  • Snap.svg - 停止在可悬停元素的子元素上重新触发悬停事件

    对于一个项目 我使用的 SVG 形状由背景多边形和背景多边形上方的一些文本 我已将其转换为路径 组成 我正在使用 Snap svg 为我的形状设置动画 当我将鼠标悬停在多边形上时 形状应该缩放到特定尺寸 包括其中的所有内容 鼠标移开时 形状
  • React-Redux:state.setIn() 和 state.set() 有什么区别?

    我见过使用setIn and set 在一些react redux代码中 state setIn state set 我在这里找到了一些文档https facebook github io immutable js https facebo
  • 防止 iOS 键盘在 cordova 3.5 中滚动页面

    我正在使用 Cordova 3 5 和 jQuery mobile 构建 iOS 应用程序 我在大部分应用程序中禁用了滚动功能 但是 当我选择输入字段时 iOS 键盘会打开并向上滚动页面 我不想要这个功能 由于输入足够高 键盘不会覆盖它 我
  • Angular - CSS - 自定义类型=文件输入,如何使用按钮而不是标签?

    我制作了一个类型为 file 的自定义输入字段 因为我不喜欢默认的输入字段 为了实现这一目标 我做了
  • 将 GMT 时间转换为当地时间

    我以这种格式从我的服务器获取 GMT 时间 Fri 18 Oct 2013 11 38 23 GMT 我的要求是使用Javascript将此时间转换为本地时间 例如 如果用户来自印度 首先我需要采用时区 5 30并将其添加到我的服务器时间并
  • 设置 cookie 时中断 JavaScript 执行

    当设置 cookie 时 是否可以始终中断浏览器开发人员工具中的 javascript 执行 无需显式设置 JS 断点 document cookie 在 html head 块的开头添加此代码片段效果很好
  • 为什么我们在打字稿中使用 HTMLInputElement ?

    我们为什么使用 document getElementById ipv as HTMLInputElement value 代替 document getElementById ipv value 功能getElementById返回具有类
  • 有没有办法在 onclick 触发时禁用 iPad/iPhone 上的闪烁/闪烁?

    所以我有一个有 onclick 事件的区域 在常规浏览器上单击时 它不会显示任何视觉变化 但在 iPad iPhone 上单击时 它会闪烁 闪烁 有什么办法可以阻止它在 iPad iPhone 上执行此操作吗 这是一个与我正在做的类似的示例
  • 页面上使用 HTML Editor Extender 进行回发会导致 IE11 中出现 JavaScript 错误

    我已将 HTML 编辑器扩展程序添加到我正在处理的页面中 现在每当我在页面上发回帖子时 都会收到以下 Javascript 错误 JavaScript 运行时错误 参数无效 之后什么也没有发生 这在 IE10 或更低版本以及我所知道的所有其
  • Firebase 函数 onWrite 未被调用

    我正在尝试使用 Firebase 函数实现一个触发器 该触发器会复制数据库中的一些数据 我想观看所有添加的内容votes user vote 结构为 我尝试的代码是 const functions require firebase func
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • GitHub Actions:如何将 toJSON() 结果传递给 shell 命令

    因此 我正在与 Github Actions 合作进行端到端测试 我正在查看的设置是让一项作业检索要测试的 url 列表 而我的第二项作业使用该列表创建一个矩阵并测试所有这些 我的问题是 当我实际运行测试脚本时 必须从命令行完成 因为我使用
  • 如何使用 crypto-js 解密 AES ECB

    我正在尝试将加密数据从 flash 客户端 发送到服务器端的 javascript 在 asp 中作为 jscript 运行 有几个 javascript Aes 库 但它们实际上没有文档记录 我正在尝试使用 crypto js 但无法让代
  • 带参数的事件监听器

    我想将参数传递给 JavaScript 中的事件侦听器 我已经找到了解决方案 但我无法理解它们为什么或如何工作以及为什么其他解决方案不起作用 我有 C C 背景 但是 Javascript 函数的执行有很大不同 您能否帮助我理解以下示例如何
  • 从 FileReader 设置背景图像样式

    我正在寻找一种解决方案 允许我从文件上传输入中获取文件并通过设置 document body style backgroundImage 来预览它 以下代码用于在 Image 元素中显示预览 function setImage id tar
  • 使用 MongoDB 和 Nodejs 插入和查询日期

    我需要一些帮助在 mongodb 和 nodejs 中按日期查找记录 我将日期添加到抓取脚本中的 json 对象 如下所示 jsonObj last updated new Date 该对象被插入到 mongodb 中 我可以看到如下 la
  • Vue.js[vuex] 如何从突变中调度?

    我有一个要应用于 json 对象的过滤器列表 我的突变看起来像这样 const mutations setStars state payload state stars payload this dispatch filter setRev

随机推荐

  • Wicket 重定向:如何传递参数并保持 URL“漂亮”?

    考虑一个重定向到另一个页面的 Wicket 网页 基于此处省略的一些逻辑 public class SomePage extends WebPage public SomePage PageParameters parameters set
  • Python ElementTree find() 在 kml 文件中不匹配

    我正在尝试使用元素树从 kml 文件中查找元素 如下所示 from xml etree ElementTree import ElementTree tree ElementTree tree parse history 03 02 201
  • 为什么在构建视图时要指定数据上下文类?

    当使用下图所示的 添加视图 对话框向 ASP NET MVC 5 项目添加新视图时 我被邀请选择一个模板和一个模型类 这使我能够快速生成一个表单来创建模型的新实例或显示模型属性的视图 但是视图为什么要关心数据上下文类是什么 在我的项目中 无
  • 在 ActionCable 中未找到订阅类“MyChannel”

    我在使用 Action Cable 时遇到问题 每当我运行程序时 我都会收到一条错误消息 Subscription找不到类ConversationChannel 当我尝试发送消息时 我收到此日志 成功升级到WebSocket REQUEST
  • 二维码回车

    如何在 QR 条形码中编码回车符 例如 如果我用 Android 扫描它 它就会出现 A B C 我努力了A d013B d013C等等 但它总是显示转义字符 在我发现的其他方法中 这些方法似乎都不起作用 您只需将正确的字节序列放入 QR
  • 正则表达式中的负向前瞻以排除 R 中的百分比 (%)

    我希望提取具有任意小数的数字 小数点两边至少各有一个数字 但是not模式后跟百分比 因此 我相信我需要一个负前瞻 这样它就可以看到数字后面是否有百分号 为了清楚起见 我想提取 123 123 但不想提取 123 123 我尝试了十几种语法安
  • 如何在tcl中搜索数字即进程ID并杀死进程ID

    我尝试搜索存储在变量 say 缓冲区中的进程 id i e 6762 nohup tcpdump ni eth0 s0 2 gt 1 null 1 6762 You have new mail in var mail root 如果匹配的话
  • 参数类型“对象?”无法分配给参数类型“Map

    我在以下行 var map Map from snap snapshot value 中的 snap snapshot value 参数上出现错误 错误是 参数类型 对象 无法分配给参数类型 Map class HomePageState
  • 用于调整图像大小和创建缩略图的 ImageMagick 或 GD 库?

    我一直在使用其他人编写的图像调整大小类来调整图像大小或创建缩略图 这是我一直在使用的类的链接 http www white hat web design co uk articles php image resizing php http
  • 使用用户窗体中的数据将值从一张纸复制到另一张纸

    我有一个用户表单 其中包含您可以填写的以下值 TextBoxLopnummer Value TextBoxFragestallare Value TextBoxMottagare Value TextBoxDatum Value Pictu
  • 在 do while 循环内检查多个 checkbox.checked 状态

    我在上一个问题中问过如何 线程化 2 个表单以同时使用 C https stackoverflow com questions 40450905 threading 2 forms to use simultaneously c sharp
  • -1 是一个神奇的数字吗?反模式?代码味道?当局的引述和指南[重复]

    这个问题在这里已经有答案了 可能的重复 不断的虐待 https stackoverflow com questions 1862593 constant abuse 我见过 1用于各种 API 最常用于搜索具有从零开始的索引的 集合 时 通
  • 从 Rails 3 升级后不允许使用 Rails 4 方法

    我有一个现有的代码库 我正在尝试将其从 Rails 3 2 升级到 Rails 4 0 我有一个名为 asset controller 的控制器 带有 create 方法 并且我的路由文件中有一个条目 resources assets 在前
  • 如何创建一个接受任何类型变量的 Java 函数?

    我想创建一个可以接受任何传入变量的函数 无论类型如何 int double String或其他对象 然后可能确定变量的类型并对该类型进行有条件的操作 我怎样才能做到这一点 重载是最推荐的选项 大多数时候您不需要接受任何类型变量的函数 但是如
  • 为什么ssd和yolo没有roi池化层?

    我们知道目标检测框架像faster rcnn and mask rcnn has an roi pooling layer or roi align layer 但是为什么ssd和yolo框架没有这样的层呢 首先我们要明白这样做的目的是什么
  • @font-face 和 .ttf 文件出现问题

    我正在尝试使用 font face 来实现我在线下载的字体 http www losttype com font name blanch http www losttype com font name blanch 并且我在让它在任何浏览器
  • 保存 ember 数据模型后的转换

    我想在创建帖子后进行转换 post new gt 单击提交 gt Rails 后端成功创建帖子并响应 json gt 重定向到新创建帖子的路径 在 ember data example github 源代码中 他们使用这种方法 transi
  • Grails JSONBuilder

    如果我有一个简单的对象 例如 class Person String name Integer age 我可以使用 JSONBuilder 轻松地将其用户定义的属性呈现为 JSON def person new Person name bo
  • Rails 在模型中验证值在数组内

    我有一个表格 我可以在其中传递一个字段命名 type我想要检查它的值是否在允许类型的数组内以便任何人不得发布不允许的类型 数组看起来像 allowed types type1 type2 type3 type4 type5 type6 ty
  • 异步顺序调用方法

    我有一个在方法中调用的方法列表 如下所示 this doOneThing someOtherObject doASecondThing this doSomethingElse 当这是同步时 它们会一个接一个地执行 这是必需的 但现在我有