如何同步一系列承诺? [英] How to synchronize a sequence of promises?

查看:123
本文介绍了如何同步一系列承诺?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个promise对象数组,必须按照它们在数组中列出的顺序进行解析,即我们不能尝试解析一个元素,直到前一个元素被解析(如方法 Promise.all([...])确实如此。

I have an array of promise objects that must be resolved in the same sequence in which they are listed in the array, i.e. we cannot attempt resolving an element till the previous one has been resolved (as method Promise.all([...]) does).

如果一个元素被拒绝,我需要链一次拒绝,而不是试图解决以下元素。

And if one element is rejected, I need the chain to reject at once, without attempting to resolve the following element.

我如何实现这个,或者是否有这样的序列的现有实现模式?

How can I implement this, or is there an existing implementation for such sequence pattern?

function sequence(arr) {
    return new Promise(function (resolve, reject) {
        // try resolving all elements in 'arr',
        // but strictly one after another;
    });
}

编辑

初始答案表明我们只能序列这些数组元素的结果,而不是它们的执行,因为它是在这样的例子中预定义的。

The initial answers suggest we can only sequence results of such array elements, not their execution, because it is predefined in such example.

然后如何以避免提前执行的方式生成一系列承诺?

But then how to generate an array of promises in such a way as to avoid early execution?

这是一个修改过的示例:

Here's a modified example:

function sequence(nextPromise) {
    // while nextPromise() creates and returns another promise,
    // continue resolving it;
}

我不想把它变成一个单独的问题,因为我相信它是同一问题的一部分。

I wouldn't want to make it into a separate question, because I believe it is part of the same problem.

解决方案

下面的一些答案和接下来的讨论有点误入歧途,但最终解决方案完全符合我的要求,是在 spex 库,作为序列的方法。该方法可以迭代一系列动态长度,并根据应用程序的业务逻辑创建promise。

Some answers below and discussions that followed went a bit astray, but the eventual solution that did exactly what I was looking for was implemented within spex library, as method sequence. The method can iterate through a sequence of dynamic length, and create promises as required by the business logic of your application.

稍后我将其转换为每个人的共享库使用。

Later on I turned it into a shared library for everyone to use.

推荐答案

下面是一些简单的例子,说明如何按序列顺序执行每个异步操作的数组(一个接一个地) )。

Here are some simple examples for how you sequence through an array executing each async operation serially (one after the other).

假设您有一系列项目:

var arr = [...];

并且,您希望对阵列中的每个项目执行特定的异步操作,一个在时间顺序,以便下一个操作在前一个操作完成之前不会开始。

And, you want to carry out a specific async operation on each item in the array, one at a time serially such that the next operation does not start until the previous one has finished.

并且,假设您有一个承诺返回函数来处理其中一个项目数组:

And, let's suppose you have a promise returning function for processing one of the items in the array:

function processItem(item) {
    // do async operation and process the result
    // return a promise
}

然后,您可以执行以下操作:

Then, you can do something like this:

function processArray(array, fn) {
    var index = 0;

    function next() {
        if (index < array.length) {
            fn(array[index++]).then(next);
        }
    }
    next();
}

processArray(arr, processItem);



手动迭代返还承诺



如果你想要从 processArray()返回一个承诺,所以你知道什么时候完成,你可以把它添加到它:

Manual Iteration Returning Promise

If you wanted a promise returned from processArray() so you'd know when it was done, you could add this to it:

function processArray(array, fn) {
    var index = 0;

    return new Promise(function(resolve, reject) {

        function next() {
            if (index < array.length) {
                fn(array[index++]).then(next, reject);
            } else {
                resolve();
            }
        }
        next();
    }
}

processArray(arr, processItem).then(function() {
    // all done here
}, function(reason) {
    // rejection happened
});

注意:这将阻止链条在第一次拒绝并将该原因传回processArray返回的承诺。

Note: this will stop the chain on the first rejection and pass that reason back to the processArray returned promise.

如果你想用promises做更多的工作,你可以链接所有的承诺:

If you wanted to do more of the work with promises, you could chain all the promises:

function processArray(array, fn) {
   return array.reduce(function(p, item) {
       return p.then(function() {
          return fn(item);
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
}, function(reason) {
    // rejection happened
});

注意:这将在第一次拒绝时停止链并将该原因传回给从 processArray()

Note: this will stop the chain on the first rejection and pass that reason back to the promise returned from processArray().

对于成功方案,从 processArray()返回的承诺将使用 fn 回调的最后解析值解决。如果你想累积一个结果列表并解决它,你可以从 fn 收集一个闭包数组中的结果,并且每次都继续返回该数组,以便最终解决将是一系列结果。

For a success scenario, the promise returned from processArray() will be resolved with the last resolved value of your fn callback. If you wanted to accumulate a list of results and resolve with that, you could collect the results in a closure array from fn and continue to return that array each time so the final resolve would be an array of results.

并且,因为现在看来你希望最终的承诺结果是一个数据数组(按顺序),这里是以前的解决方案的修订版,它产生了:

And, since it now seems apparent that you want the final promise result to be an array of data (in order), here's a revision of the previous solution that produces that:

function processArray(array, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return results;
           });
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

工作演示: http://jsfiddle.net/jfriend00/h3zaw8u8/

以及显示拒绝的工作演示: http://jsfiddle.net/jfriend00/p0ffbpoc/

And a working demo that shows a rejection: http://jsfiddle.net/jfriend00/p0ffbpoc/

并且,如果要在操作之间插入一小段延迟:

And, if you want to insert a small delay between operations:

function delay(t, v) {
    return new Promise(function(resolve) {
        setTimeout(resolve.bind(null, v), t);
    });
}

function processArrayWithDelay(array, t, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return delay(t, results);
           });
       });
   }, Promise.resolve());
}

processArray(arr, 200, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});



使用Bluebird Promise库进行迭代



Bluebird promise库有很多内置的并发控制功能。例如,要通过数组对迭代进行排序,可以使用 Promise.mapSeries()

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});

或者在迭代之间插入延迟:

Or to insert a delay between iterations:

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item).delay(100);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});



使用ES7 async / await



如果你在支持async / await的环境中进行编码,你也可以只使用常规的进行循环,然后等待循环中的一个promise,它会导致 for 循环暂停,直到一个promise被解决,然后才继续。这将有效地对您的异步操作进行排序,以便下一个操作在上一个操作完成之前不会启动。

Using ES7 async/await

If you're coding in an environment that supports async/await, you can also just use a regular for loop and then await a promise in the loop and it will cause the for loop to pause until a promise is resolved before proceeding. This will effectively sequence your async operations so the next one doesn't start until the previous one is done.

async function processArray(array, fn) {
    let results = [];
    for (let i = 0; i < array.length; i++) {
        let r = await fn(array[i]);
        results.push(r);
    }
    return results;    // will be resolved value of promise
}

// sample usage
processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});






仅供参考,我想我的 processArray()这里的函数非常类似于 Bluebird promise库中的Promise.map() ,它接受一个数组和一个promise生成函数并返回一个承诺通过一系列已解决的结果解决。


FYI, I think my processArray() function here is very similar to Promise.map() in the Bluebird promise library which takes an array and a promise producing function and returns a promise that resolves with an array of resolved results.

@ vitaly-t - 这里有一些关于你的方法的更详细的评论。欢迎您使用最适合您的代码。当我第一次开始使用promises时,我倾向于仅使用promises来完成他们所做的最简单的事情,并且当更高级的promises使用可以为我做更多的事情时,我自己写了很多逻辑。你只使用你完全熟悉的东西,除此之外,你宁愿看到你熟悉的自己的代码。这可能是人性。

@vitaly-t - Here some some more detailed comments on your approach. You are welcome to whatever code seems best to you. When I first started using promises, I tended to use promises only for the simplest things they did and write a lot of the logic myself when a more advanced use of promises could do much more of it for me. You use only what you are fully comfortable with and beyond that, you'd rather see your own code that you intimately know. That's probably human nature.

我会建议,随着我越来越了解承诺可以为我做什么,我现在想编写使用承诺的更多高级功能的代码对我来说似乎很自然,我觉得我正在建立经过良好测试的基础架构,它具有许多有用的功能。我只是要求你保持开放的态度,因为你越来越多地学习这个方向。我认为,随着您的理解的提高,迁移是一个有用且富有成效的方向。

I will suggest that as I understood more and more of what promises can do for me, I now like to write code that uses more of the advanced features of promises and it seems perfectly natural to me and I feel like I'm building on well tested infrastructure that has lots of useful features. I'd only ask that you keep your mind open as you learn more and more to potentially go that direction. It's my opinion that it's a useful and productive direction to migrate as your understanding improves.

以下是您的方法的一些具体反馈点:

Here are some specific points of feedback on your approach:

您在七个地方创建承诺

作为样式的对比,我的代码只有两个我明确创建新承诺的地方 - 一次在工厂函数中,一次初始化 .reduce()循环。在其他任何地方,我只是建立在已经通过链接到它们或在其中返回值或直接返回它们而创建的promise。您的代码具有七个唯一的位置,您可以在其中创建承诺。现在,良好的编码不是一个竞争,看你有多少地方可以创造一个承诺,但这可能会指出杠杆的差异已经创造的承诺与测试条件和创造新的承诺。

As a contrast in styles, my code has only two places where I explicitly create a new promise - once in the factory function and once to initialize the .reduce() loop. Everywhere else, I'm just building on the promises already created by chaining to them or returning values within them or just returning them directly. Your code has seven unique places where you're creating a promise. Now, good coding isn't a contest to see how few places you can create a promise, but that might point out the difference in leverage the promises that are already created versus testing conditions and creating new promises.

投掷安全是一个非常有用的功能

Promise是安全的。这意味着在promise处理程序中抛出的异常将自动拒绝该promise。如果您只是希望异常成为拒绝,那么这是一个非常有用的功能,可以利用。事实上,你会发现只是抛弃自己是一种有用的方法,可以在处理程序中拒绝,而不会创建另一个承诺。

Promises are throw-safe. That means that an exception thrown within a promise handler will automatically reject that promise. If you just want the exception to become a rejection, then this is a very useful feature to take advantage of. In fact, you will find that just throwing yourself is a useful way to reject from within a handler without creating yet another promise.

很多 Promise.resolve() Promise.reject()可能是简化的机会

Lots of Promise.resolve() or Promise.reject() is probably an opportunity for simplification

如果你看到代码有很多 Promise.resolve() Promise.reject()陈述,那么可能有机会更好地利用现有的承诺而不是创造所有这些新的承诺。

If you see code with lots of Promise.resolve() or Promise.reject() statements, then there are probably opportunities to leverage the existing promises better rather than creating all these new promises.

转为承诺

如果您不知道某件事是否返回了承诺,那么您可以将其投入承诺。然后,promise库会自行检查它是否是一个承诺,甚至它是否是与您正在使用的promise库相匹配的承诺,如果没有,则将其包装成一个。这可以节省你自己重写的大部分逻辑。

If you don't know if something returned a promise, then you can cast it to a promise. The promise library will then do it's own checks whether it is a promise or not and even whether it's the kind of promise that matches the promise library you're using and, if not, wrap it into one. This can save rewriting a lot of this logic yourself.

回复承诺的合约

在目前的许多情况下,拥有一个函数的契约是完全可行的,这个函数可以做一些异步的事情来返回一个promise。如果函数只是想做同步的事情,那么它只能返回一个已解决的promise。你似乎觉得这很麻烦,但它绝对是风吹的方式,我已经写了很多需要的代码,一旦你熟悉了承诺,感觉就很自然了。它抽象出操作是同步还是异步,并且调用者不必知道或做任何特殊的事情。这是一个很好的使用承诺。

In many cases these days, it's completely viable to have a contract for a function that may do something asynchronous to return a promise. If the function just wants to do something synchronous, then it can just return a resolved promise. You seem to feel like this is onerous, but it's definitely the way the wind is blowing and I already write lots of code that requires that and it feels very natural once you get familiar with promises. It abstracts away whether the operation is sync or async and the caller doesn't have to know or do anything special either way. This is a nice use of promises.

可以编写工厂函数只创建一个承诺

可以编写工厂函数以仅创建一个承诺,然后解析或拒绝它。这种风格也使它安全,因此工厂函数中出现的任何异常都会自动变为拒绝。它还使合约始终自动返回一个承诺。

The factory function can be written to create one promise only and then resolve or reject it. This style also makes it throw safe so any exception occuring in the factory function automatically becomes a reject. It also makes the contract to always return a promise automatic.

虽然我意识到这个工厂函数是一个占位符函数(它甚至不做任何异步),希望你可以看到它的风格:

While I realize this factory function is a placeholder function (it doesn't even do anything async), hopefully you can see the style to consider it:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("one");
                break;
            case 1:
                resolve("two");
                break;
            case 2:
                resolve("three");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

如果这些操作中的任何一个是异步的,那么他们就可以返回自己的承诺这将自动链接到这样的一个中心承诺:

If any of these operations were async, then they could just return their own promises which would automatically chain to the one central promise like this:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve($.ajax(...));
            case 1:
                resole($.ajax(...));
            case 2:
                resolve("two");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

使用拒绝处理程序只需返回promise.reject(reason)不需要

Using a reject handler to just return promise.reject(reason) is not needed

当你拥有这段代码时:

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    }, function (reason) {
        return promise.reject(reason);
    });

拒绝处理程序不添加任何值。你可以这样做:

The reject handler is not adding any value. You can instead just do this:

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    });

您已经返回 obj.then()。如果 obj 拒绝或者有任何链接到 obj 或从那时返回.then() 处理程序拒绝,然后 obj 将拒绝。因此,您无需使用拒绝创建新承诺。没有拒绝处理程序的更简单的代码用更少的代码做同样的事情。

You are already returning the result of obj.then(). If either obj rejects or if anything chained to obj or returned from then .then() handler rejects, then obj will reject. So you don't need to create a new promise with the reject. The simpler code without the reject handler does the same thing with less code.

这是你的一般架构中的一个版本尝试包含大部分这些想法的代码:

Here's a version in the general architecture of your code that tries to incorporate most of these ideas:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("zero");
                break;
            case 1:
                resolve("one");
                break;
            case 2:
                resolve("two");
                break;
            default:
                // stop further processing
                resolve(null);
                break;
        }
    });
}


// Sequentially resolves dynamic promises returned by a factory;
function sequence(factory) {
    function loop(idx, result) {
        return Promise.resolve(factory(idx)).then(function(val) {
            // if resolved value is not null, then store result and keep going
            if (val !== null) {
                result.push(val);
                // return promise from next call to loop() which will automatically chain
                return loop(++idx, result);
            } else {
                // if we got null, then we're done so return results
                return result;
            }
        });
    }
    return loop(0, []);
}

sequence(factory).then(function(results) {
    log("results: ", results);
}, function(reason) {
    log("rejected: ", reason);
});

工作演示: http://jsfiddle.net/jfriend00/h3zaw8u8/

关于此实施的一些评论:

Some comments about this implementation:


  1. Promise.resolve(factory(idx))基本上会转换工厂(idx)承诺。如果它只是一个值,则它将成为已解决的承诺,并将该返回值作为解析值。如果它已经是一个承诺,那么它只是链接到那个承诺。因此,它会替换 factory()函数的返回值上的所有类型检查代码。

  1. Promise.resolve(factory(idx)) essentially casts the result of factory(idx) to a promise. If it was just a value, then it becomes a resolved promise with that return value as the resolve value. If it was already a promise, then it just chains to that promise. So, it replaces all your type checking code on the return value of the factory() function.

工厂函数通过返回 null 或已解析值最终为 null 的承诺来表示完成。上面的演员将这两个条件映射到相同的结果代码。

The factory function signals that it is done by returning either null or a promise who's resolved value ends up being null. The above cast maps those two conditions to the same resulting code.

工厂函数自动捕获异常并将其转换为拒绝,然后由<自动处理code> sequence()函数。如果您只想中止处理并将错误反馈给第一个异常或拒绝,那么让promises执行大量错误处理是一个显着优势。

The factory function catches exceptions automatically and turns them into rejects which are then handled automatically by the sequence() function. This is one significant advantage of letting promises do a lot of your error handling if you just want to abort processing and feed the error back on the first exception or rejection.

此实现中的工厂函数可以返回promise或静态值(用于同步操作),它可以正常工作(根据您的设计请求)。

The factory function in this implementation can return either a promise or a static value (for a synchronous operation) and it will work just fine (per your design request).

我已经在工厂函数的promise回调中使用抛出异常对其进行了测试,它确实只是拒绝并传播该异常以拒绝序列承诺,并将异常作为原因。

I've tested it with a thrown exception in the promise callback in the factory function and it does indeed just reject and propagate that exception back to reject the sequence promise with the exception as the reason.

这使用了与你类似的方法(故意,试图保持你的通用架构),用于链接多个调用 loop()

This uses a similar method as you (on purpose, trying to stay with your general architecture) for chaining multiple calls to loop().

这篇关于如何同步一系列承诺?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆