用于管理多个异步 JavaScript 操作的设计模式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6659489/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 21:27:13  来源:igfitidea点击:

Design pattern for managing multiple asynchronous JavaScript operations

javascriptdesign-patternsasynchronouscallback

提问by jfriend00

I'm looking for a good design pattern for keeping track of a bunch of different asynchronous JavaScript activities (images loading, multiple AJAX calls, sequenced AJAX calls, etc…) that's better than just a lot of custom callbacks and custom state variables. What would you suggest I use? Is there any type of queue system with the ability to have logic beyond just sequencing?

我正在寻找一种很好的设计模式来跟踪一堆不同的异步 JavaScript 活动(图像加载、多个 AJAX 调用、有序的 AJAX 调用等),这比很多自定义回调和自定义状态变量更好。你建议我用什么?是否有任何类型的队列系统能够拥有超越排序的逻辑?

Example Problem

示例问题

I have a startup sequence that involves a number of asynchronous processes (loading images, waiting for timers, making some ajax calls, doing some initialization). Some of the asynch processes can be launched to run at the same time (loading of images, AJAX calls) and some have to be sequenced (run AJAX call #1, then AJAX call #2). Right now, I've got everything running off callback functions and a bunch of global state that keeps track of what has or hasn't completed. It works, but it's quite messy and I've had a few bugs because of the complication of making sure you handle all the sequencing possibilities right and error conditions.

我有一个启动序列,它涉及许多异步进程(加载图像、等待计时器、进行一些 ajax 调用、进行一些初始化)。一些异步进程可以启动以同时运行(加载图像、AJAX 调用),而一些必须排序(运行 AJAX 调用 #1,然后是 AJAX 调用 #2)。现在,我已经让回调函数和一堆全局状态运行起来,以跟踪已完成或未完成的内容。它可以工作,但它非常混乱,并且我遇到了一些错误,因为确保您正确处理所有排序可能性和错误条件很复杂。

When you have problems, it's also quite a pain to debug because it's like the Heisenberg uncertainty principle. As soon as you set a breakpoint anywhere in the sequence, everything changes. You have to put all sorts of debugging statements in the code to try to discern what's happening.

当你遇到问题时,调试起来也很痛苦,因为它就像海森堡不确定性原理。一旦您在序列中的任何位置设置断点,一切都会改变。您必须在代码中放入各种调试语句以尝试辨别发生了什么。

Here's a more specific description:

这是更具体的描述:

  1. There are three images loading. As soon as one specific image is loaded, I want to display it. Once it's been displayed for a certain amount of time, I want to display the second image. The third one goes in a queue for later display.

  2. There are three AJAX calls that must happen in consecutive order (the output of one is used as part of the input of the next).

  3. When the AJAX calls are done, there's a bunch of JS processing of the results to do and then two more images need to get loaded.

  4. When those two images are loaded, there's some more display stuff to do.

  5. When all that is done, you examine how long one of the images has been displayed and if enough time has passed, display the next image. If not, wait some more time before displaying the next image.

  1. 加载了三个图像。一旦加载了一个特定的图像,我就想显示它。一旦显示了一定时间,我想显示第二张图像。第三个进入队列以供稍后显示。

  2. 有三个 AJAX 调用必须按连续顺序发生(一个的输出用作下一个的输入的一部分)。

  3. 当 AJAX 调用完成后,需要对结果进行一堆 JS 处理,然后需要加载另外两个图像。

  4. 加载这两个图像后,还有一些显示内容要做。

  5. 完成所有这些后,您检查其中一个图像已显示多长时间,如果时间足够长,则显示下一个图像。如果没有,请再等待一段时间,然后再显示下一张图像。

Each step has both success and error handlers. Some of the error handlers kick off alternate code that can still continue successfully.

每个步骤都有成功和错误处理程序。一些错误处理程序启动了仍然可以成功继续的替代代码。

I don't expect anyone to follow the exact process here, but just to give folks an idea of the type of logic between the steps.

我不希望任何人遵循这里的确切过程,而只是让人们了解步骤之间的逻辑类型。

Edit: I came across YUI's AsyncQueuewhich isn't a complete solution for the type of problem I have, but is in the same space. It seems to be more for sequencing or ordering a bunch of async operations, but I don't see how it helps with the type of decision making I have.

编辑:我遇到了YUI 的 AsyncQueue,它不是我所遇到的问题类型的完整解决方案,但位于同一空间中。它似乎更适合对一堆异步操作进行排序或排序,但我看不出它对我的决策类型有何帮助。

采纳答案by jfriend00

With promises now standard in ES6 and many good promise libraries extending that for both new features and backwards compatibility, it seems that promises are the way to go here.

Promise 现在是 ES6 的标准,并且有许多优秀的 Promise 库扩展了它的新特性和向后兼容性,似乎 Promise 是通往这里的路。

The solution starts with taking each async operation and creating a wrapper that returns a promise:

该解决方案首先采用每个异步操作并创建一个返回承诺的包装器:

For loading an image:

加载图像:

function loadImage(url) {
    return new Promise(function(resolve, reject) {
        var img = new Image();
        img.onload = function() {
            resolve(img);
        };
        img.onerror = img.onabort = function() {
            reject(url);
        };
        img.src = url;
    });
}

For making an Ajax call (simplified version):

进行 Ajax 调用(简化版):

function ajaxGet(url) {
    return new Promise(function(resolve, reject) {
        var req = new XMLHttpRequest();
        req.addEventListener("load", resolve);
        req.addEventListener("error", reject);
        req.addEventListener("abort", reject);
        req.open("GET", url);
        req.send();
    });
}

For waiting a particular amount of time before executing an operation, you can even making a promise version of setTimeout()so it can be chained with other promise operations:

为了在执行操作之前等待特定的时间,您甚至可以制作一个承诺版本,setTimeout()以便它可以与其他承诺操作链接:

// delay, return a promise
// val is optional
function delay(t, val) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(val);
        }, t);
    });
}

Now, these can be combined to create the logic the question asked for:

现在,可以将这些组合起来创建问题要求的逻辑:

There are three images loading. As soon as one specific image is loaded, I want to display it. Once it's been displayed for a certain amount of time, I want to display the second image. The third one goes in a queue for later display.

加载了三个图像。一旦加载了一个特定的图像,我就想显示它。一旦显示了一定时间,我想显示第二张图像。第三个进入队列以供稍后显示。

// start all three images loading in parallel, get a promise for each one
var imagePromises = [url1, url2, url3].map(function(item) {
    return loadImage(item);
});

// display the three images in sequence with a delay between them
imagePromises.reduce(function(p, item) {
    return p.then(function() {
        // when the next image is ready display it
        return item.then(function(img) {
            displayImage(img);
            return delay(15 * 1000);
        });
    });
}, Promise.resolve());

This use of .reduce()shows a classic design pattern for sequencing a series of operations on an array using promises.

这种使用.reduce()展示了一种经典的设计模式,用于使用 Promise 对数组上的一系列操作进行排序。

There are three AJAX calls that must happen in consecutive order (the output of one is used as part of the input of the next).

有三个 AJAX 调用必须按连续顺序发生(一个的输出用作下一个的输入的一部分)。

and

When the AJAX calls are done, there's a bunch of JS processing of the results to do and then two more images need to get loaded.

当 AJAX 调用完成后,需要对结果进行一堆 JS 处理,然后需要加载另外两个图像。

var p = ajaxGet(url1).then(function(results1) {
    // do something with results1
    return ajaxGet(url2);
}).then(function(results2) {
    // do something with results2
    return ajaxGet(url3); 
}).then(function(results3) {
    // process final results3 here
    // now load two images and when they are loaded do some display stuff
    return Promise.all(loadImage(imgx), loadImage(imgy)).then(function(imgs) {
        doSomeDisplayStuff(imgs);
    });
});

回答by Anurag

Take a look at the concept of Promises/A. jQuery implements this with the jQuery.Deferredobject.

看看Promises/A的概念。jQuery 使用jQuery.Deferred对象来实现这一点。

Here is a nice articleshowing how it could be useful for your situation. I asked a similar questiona while back.

这是一篇很好的文章,展示了它如何对您的情况有用。不久前我问了一个类似的问题

回答by Josh Habdas

Though Promisesafford us cleaner code we can do better with Generators. I wrote up a post on how to use Generators in Tame Async JavaScript with ES6. Using the pattern described will make handling complex async interactions easier to reason about and sets up a mental model for awaitcapabilities planned for ES7.

尽管Promise为我们提供了更简洁的代码,但我们可以使用Generators做得更好。我写了一篇关于如何在带有 ES6 的 Tame Async JavaScript 中使用生成器的文章。使用所描述的模式将使处理复杂的异步交互更容易推理,并await为 ES7 计划的功能建立一个心智模型。