简体   繁体   English

Javascript解析已按顺序填充的承诺数组中的第一个元素

[英]Javascript resolve first element in array of promises that has been fullfilled in order

I'm doing multiple requests to different APIs (simulated by the delayedPromise function) which each take a certain amount of time to fulfill.我正在向不同的 API(由 delayPromise 函数模拟)发出多个请求,每个请求都需要一定的时间来完成。 If one of them fulfills I return the value it resolves and that's it.如果其中之一满足我返回它解决的值,就是这样。 I'm currently doing those one after the other since the first request would be the most accurate while the second would be less accurate etc. So if the first request fails and the second fulfills I return the second我目前正在一个接一个地做这些,因为第一个请求将是最准确的,而第二个请求会不太准确等等。所以如果第一个请求失败而第二个请求满足我返回第二个

To speed things up I thought about parallelizing those requests.为了加快速度,我考虑将这些请求并行化。 To accomplish that I'm creating an array that holds the promises and it awaits the result of the first element and if that fails it awaits the result of the second element etc...为了实现这一点,我正在创建一个包含承诺的数组,它等待第一个元素的结果,如果失败,它等待第二个元素的结果等等......

Here's the code:这是代码:

function promiseTest() {
    return new Promise(function (resolve, reject) {
        let promises = []
        new Promise(async function (resolve, reject) {
            promises.push(delayedPromise(true, 10000, "1")) //If true resolves after 10000 ms; false = reject
            promises.push(delayedPromise(false, 1000, "2"))
            promises.push(delayedPromise(false, 1000, "3"))
            let answer;
            let answer2
            let answer3;
            answer = await promises[0].catch(async err => {
                answer2 = await promises[1].catch(async err => {
                    answer3 = await promises[2].catch(err => { reject(err) })
                })
            })
            if (answer != undefined) {
                resolve(answer)
            }
            else if (answer2 != undefined) {
                resolve(answer2)
            }
            else if (answer3 != undefined) {
                resolve(answer3)
            }

        }).then(res => resolve(res)).catch(err => {reject(err)})

    })
}

function delayedPromise(bool, time,string ="") {
    return new Promise(function (resolve, reject) {
        if (bool) {
            setTimeout(resolve(string), time)
        }
        else {
            setTimeout(reject(), time)
        }
    })
}

The issue is that while this approach works it's问题是,虽然这种方法有效

A: Most likely not the best solution and hardly scalable A:很可能不是最好的解决方案,而且很难扩展

and B: The exact code above is creating a bunch of uncaught exceptions errors.和 B:上面的确切代码正在创建一堆未捕获的异常错误。 I believe this is when I receive a result for answer1 I'm not handling any of the rejections the other functions returned.我相信这是当我收到 answer1 的结果时,我没有处理其他函数返回的任何拒绝。

Is there any better way to do these kinds of things/ fix the above mentioned concerns?有没有更好的方法来做这些事情/解决上述问题? I'd appreciate any ideas or improvements!我很感激任何想法或改进! Many thanks in advance提前谢谢了

PS: If you can phrase my title better please let me know I'm kind of struggling to convey my issue in one sentence PS:如果你能更好地表达我的标题,请让我知道我有点努力用一句话来表达我的问题

There is no builtin for this.没有内置的。 Promise.any and Promise.allSettled are quite similar but do not do exactly what you want. Promise.anyPromise.allSettled非常相似,但并不完全符合您的要求。 You'll have to build it yourself indeed.您确实必须自己构建它。 This is what I would do:这就是我会做的:

async function first(iterable) {
    const promises = [];
    for (const thenable of iterable) {
        const promise = Promise.resolve(thenable);
        promises.push(promise);
        promise.catch(() => {}); // ignore rejections, we'll handle them later
    }
    const errors = [];
    for (const promise of promises) {
        try {
            return await promise;
        } catch(error) {
            errors.push(error);
        }
    }
    throw new AggregateError(errors);
}

Code in the question simplifies to:问题中的代码简化为:

function promiseTest() {
    let promises = [
        delayedPromise(true, 10000, '1'),
        delayedPromise(false, 1000, '2'),
        delayedPromise(false, 1000, '3')
    ];
    return promises[0]
    .catch(() => promises[1])
    .catch(() => promises[2]);
}

As soon as a successful promise is encountered, the chain will stop catching and the successful result will drop all the way through to be delivered (promise-wrpped) to promiseTest's caller.一旦遇到成功的 promise,链将停止捕获,并且成功的结果将一直传递(promise-wrpped)到 promiseTest 的调用者。

The catch-chain could also be written as follows ...捕获链也可以写成如下...

Promise.reject()
.catch(() => promises[0])
.catch(() => promises[1])
.catch(() => promises[2]);

... which helps in formulating a proceduralized version for an array of any length, as follows: ...这有助于为任意长度的数组制定程序化版本,如下所示:

function promiseTest() {
    let promises = [...]; // array of Promises (any length)
    return promises.reduce((p_, p) => p_.catch(() => p), Promise.reject());
}

Assuming that measures to avoid unhandled rejections are not taken in the formulation of the promises array, you can do so (with a series of "branch-catches") in the reduction ...假设在promises数组的制定中没有采取避免未处理拒绝的措施,您可以在减少中这样做(使用一系列“分支捕获”)......

function promiseTest() {
    let promises = [...]; // array of Promises (any length).
    return promises.reduce((p_, p) => {
        p.catch(() => null); // avoid unhandled rejections.
        return p_.catch(() => p);
    }, Promise.reject());
}

I think weighting the result to favor promises in order while still running them all in parallel (for best end-to-end time) will require building a special loop.我认为按顺序对结果进行加权以支持承诺,同时仍然并行运行它们(以获得最佳端到端时间)将需要构建一个特殊循环。 I think you can do it like this:我认为你可以这样做:

const errResult = Symbol('errResult');

// fns is an array of functions to call
// args is a array of arguments to pass to each function (same args to each)
async function getResult(fns, args) {
    // start all the async operations to run in parallel
    const promises = fns.map(fn => {
        // Rejections will resolve with a sentinel value so we can detect them
        // This helps us avoid ever getting an unhandled rejection error
        // on promises that reject after we've already found our desired value
        return fn(...args).catch(err => {
            return errResult;
        });
    });
    // now await them in order so you can find the first
    // one that completes (in order)
    // Promises that rejected have been converted into an errResult
    // resolve to help in avoiding unhandled rejections
    // after we return with our good first result
    for (let p of promises) {
        let result = await p;
        if (result !== errResult) {
            return result;
        }
    }
    throw new Error('no results');
}


// sample usage:
getResult([fn1, fn2, fn3, fn4], [argA,argB]).then(result => {
    console.log(result);
}).catch(err => {
    console.log("no results found");
});

If you want different args to each function, then just build that into fn1, fn2, etc... so they call the root function with their desired arguments and then let the args array be an empty array.如果您希望每个函数使用不同的 args,则只需将其构建到 fn1、fn2 等中……这样它们就会使用所需的参数调用根函数,然后让 args 数组成为空数组。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM