multithreading 如何同步 JavaScript 回调?

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

How to sync JavaScript callbacks?

javascriptmultithreadingcallbacksynchronization

提问by Xavier_Ex

I've been developing in JavaScript for quite some time but net yet a cowboy developer, as one of the many things that always haunts me is synching JavaScript's callbacks.

我已经用 JavaScript 开发了很长一段时间,但我还是一个牛仔开发者,因为总是困扰我的许多事情之一就是同步 JavaScript 的回调。

I will describe a generic scenario when this concern will be raised: I have a bunch of operations to perform multiple times by a for loop, and each of the operations has a callback. After the for loop, I need to perform another operation but this operation can only execute successfully if all the callbacks from the for loop are done.

我将描述出现此问题时的一般情况:我有一堆操作要通过 for 循环执行多次,并且每个操作都有一个回调。在 for 循环之后,我需要执行另一个操作,但只有在 for 循环中的所有回调都完成后,此操作才能成功执行。

Code Example:

代码示例:

 for ... in ... {
   myFunc1(callback); // callbacks are executed asynchly
 }

 myFunc2(); // can only execute properly if all the myFunc1 callbacks are done

Suggested Solution:

建议的解决方案:

Initiate a counter at the beginning of the loop holding the length of the loop, and each callback decrements that counter. When the counter hits 0, execute myFunc2. This is essentially to let the callbacks know if it's the last callback in sequence and if it is, call myFunc2 when it's done.

在循环开始时启动一个计数器来保存循环的长度,每个回调都会递减该计数器。当计数器达到 0 时,执行 myFunc2。这本质上是让回调知道它是否是序列中的最后一个回调,如果是,则在完成后调用 myFunc2。

Problems:

问题:

  1. A counter is needed for every such sequence in your code, and having meaningless counters everywhere is not a good practice.
  2. If you recall how thread conflicts in classical synchronization problem, when multiple threads are all calling var-- on the same var, undesirable outcomes would occur. Does the same happen in JavaScript?
  1. 代码中的每个这样的序列都需要一个计数器,并且到处都有无意义的计数器并不是一个好习惯。
  2. 如果您还记得经典同步问题中的线程冲突是如何发生的,当多个线程都在同一个 var 上调用 var-- 时,会出现不希望的结果。JavaScript 中也会发生同样的情况吗?

Ultimate Question:

终极问题:

Is there a better solution?

有更好的解决方案吗?

采纳答案by Ja?ck

The good news is that JavaScript is single threaded; this means that solutions will generally work well with "shared" variables, i.e. no mutex locks are required.

好消息是 JavaScript 是单线程的。这意味着解决方案通常适用于“共享”变量,即不需要互斥锁。

If you want to serialize asynch tasks, followed by a completion callback you could use this helper function:

如果要序列化异步任务,然后是完成回调,则可以使用此辅助函数:

function serializeTasks(arr, fn, done)
{
    var current = 0;

    fn(function iterate() {
        if (++current < arr.length) {
            fn(iterate, arr[current]);
        } else {
            done();
        }
    }, arr[current]);
}

The first argument is the array of values that needs to be passed in each pass, the second argument is a loop callback (explained below) and the last argument is the completion callback function.

第一个参数是需要在每次传递中传递的值的数组,第二个参数是循环回调(下面解释),最后一个参数是完成回调函数。

This is the loop callback function:

这是循环回调函数:

function loopFn(nextTask, value) {
    myFunc1(value, nextTask);
}

The first argument that's passed is a function that will execute the next task, it's meant to be passed to your asynch function. The second argument is the current entry of your array of values.

传递的第一个参数是一个将执行下一个任务的函数,它旨在传递给您的异步函数。第二个参数是值数组的当前条目。

Let's assume the asynch task looks like this:

让我们假设异步任务如下所示:

function myFunc1(value, callback)
{
  console.log(value);
  callback();
}

It prints the value and afterwards it invokes the callback; simple.

它打印值,然后调用回调;简单的。

Then, to set the whole thing in motion:

然后,让整个事情动起来:

serializeTasks([1,2, 3], loopFn, function() {
    console.log('done');
});

Demo

演示

To parallelize them, you need a different function:

要并行化它们,您需要一个不同的函数:

function parallelizeTasks(arr, fn, done)
{
    var total = arr.length,
    doneTask = function() {
      if (--total === 0) {
        done();
      }
    };

    arr.forEach(function(value) {
      fn(doneTask, value);
    });
}

And your loop function will be this (only parameter name changes):

您的循环函数将是这样的(仅参数名称更改):

function loopFn(doneTask, value) {
    myFunc1(value, doneTask);
}

Demo

演示

回答by icktoofay

The second problem is not really a problem as long as every one of those is in a separate function and the variable is declared correctly (with var); local variables in functions do not interfere with each other.

第二个问题并不是真正的问题,只要这些问题中的每一个都在一个单独的函数中并且正确声明了变量(使用var);函数中的局部变量不会相互干扰。

The first problem is a bit more of a problem. Other people have gotten annoyed, too, and ended up making libraries to wrap that sort of pattern for you. I like async. With it, your code might look like this:

第一个问题是一个更大的问题。其他人也很恼火,最终制作了库来为您包装这种模式。我喜欢async。有了它,您的代码可能如下所示:

async.each(someArray, myFunc1, myFunc2);

It offers a lot of other asynchronous building blocks, too. I'd recommend taking a look at it if you're doing lots of asynchronous stuff.

它也提供了许多其他异步构建块。如果你正在做很多异步的事情,我建议你看看它。

回答by jinpwang

You can achieve this by using a jQuery deferred object.

您可以通过使用 jQuery 延迟对象来实现这一点。

var deferred = $.Deferred();
var success = function () {
    // resolve the deferred with your object as the data
    deferred.resolve({
        result:...;
    });
};

回答by c-smile

With this helper function:

使用这个辅助函数:

function afterAll(callback,what) {
  what.counter = (what.counter || 0) + 1;
  return function() {
    callback(); 
    if(--what.counter == 0) 
      what();
  };
}

your loop will look like this:

你的循环看起来像这样:

function whenAllDone() { ... }
for (... in ...) {
  myFunc1(afterAll(callback,whenAllDone)); 
}

here afterAllcreates proxy function for the callback, it also decrements the counter. And calls whenAllDone function when all callbacks are complete.

这里afterAll为回调创建代理函数,它也会递减计数器。并在所有回调完成后调用 whenAllDone 函数。

回答by user1462586

single thread is not always guaranteed. do not take it wrong.

单线程并不总是有保证的。不要误会。

Case 1: For example, if we have 2 functions as follows.

案例 1:例如,如果我们有 2 个函数,如下所示。

var count=0;
function1(){
  alert("this thread will be suspended, count:"+count);
}
function2(){
  //anything
  count++;
  dump(count+"\n");
}

then before function1 returns, function2 will also be called, if 1 thread is guaranteed, then function2 will not be called before function1 returns. You can try this. and you will find out count is going up while you are being alerted.

那么在function1返回之前,function2也会被调用,如果保证有1个线程,那么function2在function1返回之前不会被调用。你可以试试这个。当您收到警报时,您会发现计数正在上升。

Case 2: with Firefox, chrome code, before 1 function returns (no alert inside), another function can also be called.

案例2:用Firefox,chrome代码,在1个函数返回之前(里面没有alert),也可以调用另一个函数。

So a mutex lock is indeed needed.

所以确实需要互斥锁。

回答by Elmer

There are many, many ways to achieve this, I hope these suggestions help!

有很多很多方法可以实现这一点,我希望这些建议有帮助!

First, I would transform the callback into a promise! Here is one way to do that:

首先,我会将回调转换为承诺!这是一种方法:

function aPromise(arg) {
    return new Promise((resolve, reject) => {
        aCallback(arg, (err, result) => {
            if(err) reject(err);
            else resolve(result);
        });
    })
}

Next, use reduce to process the elements of an array one by one!

接下来使用reduce对数组的元素进行一一处理!

const arrayOfArg = ["one", "two", "three"];
const promise = arrayOfArg.reduce(
    (promise, arg) => promise.then(() => aPromise(arg)), // after the previous promise, return the result of the aPromise function as the next promise
    Promise.resolve(null) // initial resolved promise
    );
promise.then(() => {
    // carry on
});

If you want to process all elements of an array at the same time, use map an Promise.all!

如果要同时处理数组的所有元素,请使用 map an Promise.all!

const arrayOfArg = ["one", "two", "three"];
const promise = Promise.all(arrayOfArg.map(
    arg => aPromise(arg)
));
promise.then(() => {
    // carry on
});

If you are able to use async / await then you could just simply do this:

如果您能够使用 async / await 那么您可以简单地执行以下操作:

const arrayOfArg = ["one", "two", "three"];
for(let arg of arrayOfArg) {
    await aPromise(arg); // wow
}

// carry on

You might even use my very cool synchronize-asynclibrary like this:

你甚至可以像这样使用我非常酷的同步异步库:

const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context

for(let arg of arrayOfArg) {
    synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}

join(context).then(() => { // join will resolve when all calls in the context are finshed
    // carry on
});

And last but not least, use the fine asynclibrary if you really don't want to use promises.

最后但并非最不重要的一点是,如果您真的不想使用Promise,请使用良好的异步库。

const arrayOfArg = ["one", "two", "three"];
async.each(arrayOfArg, aCallback, err => {
    if(err) throw err; // handle the error!
    // carry on
});