Javascript 如何使用 q.js 承诺处理多个异步操作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13597909/
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-08-24 12:26:47  来源:igfitidea点击:

how to use q.js promises to work with multiple asynchronous operations

javascriptasynchronouscallbacktask-queue

提问by Kim Stacks

Note: This question is also cross-posted in Q.js mailing list over here.

注意:此问题也在此处的Q.js 邮件列表中交叉发布。



i had a situation with multiple asynchronous operations and the answer I acceptedpointed out that using Promises using a library such as q.js would be more beneficial.

我遇到了多个异步操作的情况,我接受答案指出使用 Promises 使用诸如 q.js 之类的库会更有益。

I am convinced to refactor my code to use Promises but because the code is pretty long, i have trimmed the irrelevant portions and exported the crucial parts into a separate repo.

我确信重构我的代码以使用 Promises 但因为代码很长,我已经修剪了不相关的部分并将关键部分导出到一个单独的 repo 中。

The repo is hereand the most important file is this.

回购在这里,最重要的文件是这个.

The requirement is that I want pageSizesto be non-empty after traversing all the dragged'n dropped files.

要求是在遍历所有拖放的文件后,我希望pageSizes为非空。

The problem is that the FileAPI operations inside getSizeSettingsFromPagefunction causes getSizeSettingsFromPageto be async.

问题是getSizeSettingsFromPage函数中的 FileAPI 操作导致getSizeSettingsFromPage异步。

So I cannot place checkWhenReady();like this.

所以我不能放置checkWhenReady(); 像这样。

function traverseFiles() {
  for (var i=0, l=pages.length; i<l; i++) {
    getSizeSettingsFromPage(pages[i], calculateRatio);   
  }
  checkWhenReady(); // this always returns 0.
}

This works, but it is not ideal. I prefer to call checkWhenReady just ONCE after all the pageshave undergone this function calculateRatio successfully.

这有效,但并不理想。我更喜欢在所有pages成功执行此函数后调用 checkWhenReady 一次。

function calculateRatio(width, height, filename) {
  // .... code 
  pageSizes.add(filename, object);
  checkWhenReady(); // this works but it is not ideal. I prefer to call this method AFTER all the `pages` have undergone calculateRatio
  // ..... more code...
}

How do I refactor the code to make use of Promises in Q.js?

如何重构代码以在 Q.js 中使用 Promises?

回答by Nathan Wall

My suggestions to get this working with Q.js are below. The key is that anytime you want to do something asynchronously, you should return a promise, and once the task is completed you should resolve that promise. That allows the callers of the function to listen for the task to be completed and then do something else.

我对使用 Q.js 进行此操作的建议如下。关键是任何时候你想异步做某事,你应该返回一个承诺,一旦任务完成,你应该解决这个承诺。这允许函数的调用者监听要完成的任务,然后做其他事情。

As before, I have commented my changes with // ***. Let me know if you have any further questions.

和以前一样,我用// ***. 如果您有任何其他问题,请告诉我。

        function traverseFiles() {
            // *** Create an array to hold our promises
            var promises = [ ];
            for (var i=0, l=pages.length; i<l; i++) {
                // *** Store the promise returned by getSizeSettingsFromPage in a variable
                promise = getSizeSettingsFromPage(pages[i]);
                promise.then(function(values) {
                    var width = values[0],
                        height = values[1],
                        filename = values[2];
                    // *** When the promise is resolved, call calculateRatio
                    calculateRatio(width, height, filename);
                });
                // *** Add the promise returned by getSizeSettingsFromPage to the array
                promises.push(promise);
            }
            // *** Call checkWhenReady after all promises have been resolved
            Q.all(promises).then(checkWhenReady);
        }

        function getSizeSettingsFromPage(file) {
            // *** Create a Deferred
            var deferred = Q.defer();
            reader = new FileReader();
            reader.onload = function(evt) {
                var image = new Image();
                image.onload = function(evt) {
                    var width = this.width;
                    var height = this.height;
                    var filename = file.name;
                    // *** Resolve the Deferred
                    deferred.resolve([ width, height, filename ]);
                };
                image.src = evt.target.result;
            };
            reader.readAsDataURL(file);
            // *** Return a Promise
            return deferred.promise;
        }


Edit

编辑

defercreates a Deferred, which contains two parts, a promiseand the resolvefunction. The promiseis returned by getSizeSettingsFromPage. Basically returning a promise is a way for a function to say "I'll get back to you later." Once the function has completed it's task (in this case once the image.onloadevent has fired) the resolvefunction is used to resolve the promise. That indicates to anything waiting on the promise that the task has been completed.

defer创建一个Deferred,它包含两部分, apromiseresolve函数。由promise返回getSizeSettingsFromPage。基本上返回一个 promise 是一个函数说“我稍后会回复你”的一种方式。一旦函数完成了它的任务(在这种情况下,一旦image.onload事件被触发),该resolve函数将用于解决承诺。这表明任何等待任务已完成的承诺。

Here's a simpler example:

这是一个更简单的例子:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

addAsync(3, 4).then(function(result) {
    console.log(result);
});
// logs 7 after 2 seconds

The addAsyncfunction adds two numbers but it waits 2 seconds before adding them. Since it's asynchronous, it returns a promise (deferred.promse) and resolves the promise after the 2 second wait (deferred.resolve). The thenmethod can be called on a promise and passed a callback function to be executed after the promise has been resolved. The callback function is passed in the resolution value of the promise.

addAsync函数添加两个数字,但在添加它们之前等待 2 秒。由于它是异步的,因此它返回一个承诺 ( deferred.promse) 并在 2 秒等待 ( deferred.resolve)后解决该承诺。then可以在承诺上调用该方法,并传递一个回调函数以在承诺解决后执行。回调函数在promise 的解析值中传递。

In your case, we had an array of promises and we needed to wait for allof them to be done before executing a function, so we used Q.all. Here's an example:

在你的例子中,我们有一个 promise 数组,我们需要在执行函数之前等待所有这些都完成,所以我们使用了Q.all。下面是一个例子:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

Q.all([
    addAsync(1, 1),
    addAsync(2, 2),
    addAsync(3, 3)
]).spread(function(result1, result2, result3) {
    console.log(result1, result2, result3);
});
// logs "2 4 6" after approximately 2 seconds

回答by hugomg

Looks like you should use the Q.allfunction to create a master promise corresponding to when all the getSizeSettings promises are fufilled.

看起来您应该使用该Q.all函数来创建与所有 getSizeSettings 承诺都完成时对应的主承诺。

https://github.com/kriskowal/q#combination

https://github.com/kriskowal/q#combination

var ps = [];
for (var i=0, l=pages.length; i<l; i++) {
   ps[i] = getSizeSettingsFromPage(pages[i], calculateRatio);   
}

Q.all(ps).then(function(){ callWhenReady() })

Most promise libraries should provide a similar method to do this kind of synchronization. If you ever come across one that does not what you could do is hook each individual promise to a callback that increments a shared counter when its called. When your counter reaches nyou know that you already resolved all promises so you can have the incrementor callback call the "real" callback as well.

大多数承诺库应该提供类似的方法来进行这种同步。如果您遇到过无法做的事情,那就是将每个单独的承诺挂钩到一个回调,该回调在调用时增加共享计数器。当您的计数器到达时,n您知道您已经解决了所有承诺,因此您可以让 incrementor 回调也调用“真实”回调。

//If you did not have Q.all available
//Or had to code this without a promise library

var to_go = pages.length;
for (var i=0, l=pages.length; i<l; i++) {
   getSizeSettingsFromPage(pages[i], calculateRatio)
   .then(function(){
       to_go--;
       if(to_go == 0){
           callWhenReady()
       }
   });
}

Note that in these cases until now the async calls are allowed to run parallel. If you need them to run sequentially then usually the only way is to rewrite the for loop as a recursive function

请注意,到目前为止,在这些情况下,异步调用允许并行运行。如果您需要它们按顺序运行,那么通常唯一的方法是将 for 循环重写为递归函数

var go = function(i){
    if(i>=pages.length){
        return call_next_step()
    }else{
        return do_ith_calculation(i)
        .then(function(){
            return go(i+1)
        })
    }
};
go(0);