Javascript 等到所有承诺完成,即使有些被拒绝

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

Wait until all promises complete even if some rejected

javascriptpromisees6-promise

提问by Nathan Hagen

Let's say I have a set of Promises that are making network requests, of which one will fail:

假设我有一组Promise发出网络请求的s,其中一个会失败:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

Let's say I want to wait until all of these have finished, regardless of if one has failed. There might be a network error for a resource that I can live without, but which if I can get, I want before I proceed. I want to handle network failures gracefully.

假设我想等到所有这些都完成,无论是否失败。对于我可以没有的资源,可能存在网络错误,但如果我能得到,我需要在继续之前。我想优雅地处理网络故障。

Since Promises.alldoesn't leave any room for this, what is the recommended pattern for handling this, without using a promises library?

既然Promises.all没有为此留出任何空间,那么在不使用承诺库的情况下处理这个问题的推荐模式是什么?

采纳答案by Nathan Hagen

Benjamin's answer offers a great abstraction for solving this issue, but I was hoping for a less abstracted solution. The explicit way to to resolve this issue is to simply call .catchon the internal promises, and return the error from their callback.

本杰明的回答为解决这个问题提供了一个很好的抽象,但我希望有一个不那么抽象的解决方案。解决这个问题的明确方法是简单地调用.catch内部承诺,并从他们的回调中返回错误。

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));


Taking this one step further, you could write a generic catch handler that looks like this:

更进一步,您可以编写一个通用的 catch 处理程序,如下所示:

const catchHandler = error => ({ payload: error, resolved: false });

then you can do

那么你可以做

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

The problem with this is that the caught values will have a different interface than the non-caught values, so to clean this up you might do something like:

问题在于捕获的值与未捕获的值将具有不同的接口,因此要清理它,您可能会执行以下操作:

const successHandler = result => ({ payload: result, resolved: true });

So now you can do this:

所以现在你可以这样做:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Then to keep it DRY, you get to Benjamin's answer:

然后为了保持干燥,你会得到本杰明的回答:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

where it now looks like

现在的样子

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]


The benefits of the second solution are that its abstracted and DRY. The downside is you have more code, and you have to remember to reflect all your promises to make things consistent.

第二种解决方案的好处是它的抽象和 DRY。缺点是你有更多的代码,你必须记住反映你所有的承诺,使事情保持一致。

I would characterize my solution as explicit and KISS, but indeed less robust. The interface doesn't guarantee that you know exactly whether the promise succeeded or failed.

我会将我的解决方案描述为明确的和 KISS,但确实不那么健壮。该接口不能保证您确切知道承诺是成功还是失败。

For example you might have this:

例如你可能有这个:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

This won't get caught by a.catch, so

这不会被抓住a.catch,所以

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

There's no way to tell which one was fatal and which was wasn't. If that's important then you're going to want to enforce and interface that tracks whether it was successful or not (which reflectdoes).

没有办法分辨哪个是致命的,哪个不是。如果这很重要,那么您将需要强制执行和跟踪它是否成功的接口(reflect确实如此)。

If you just want to handle errors gracefully, then you can just treat errors as undefined values:

如果您只想优雅地处理错误,那么您可以将错误视为未定义的值:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

In my case, I don't need to know the error or how it failed--I just care whether I have the value or not. I'll let the function that generates the promise worry about logging the specific error.

就我而言,我不需要知道错误或它是如何失败的——我只关心我是否拥有价值。我会让生成承诺的函数担心记录特定错误。

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

That way, the rest of the application can ignore its error if it wants, and treat it as an undefined value if it wants.

这样,应用程序的其余部分可以根据需要忽略其错误,并根据需要将其视为未定义的值。

I want my high level functions to fail safely and not worry about the details on why its dependencies failed, and I also prefer KISS to DRY when I have to make that tradeoff--which is ultimately why I opted to not use reflect.

我希望我的高级函数安全地失败,而不用担心它的依赖项失败的原因的细节,而且当我必须做出权衡时,我也更喜欢 KISS 而不是 DRY——这就是我最终选择不使用reflect.

回答by Benjamin Gruenbaum

Update, you probably want to use the built-in native Promise.allSettled:

更新,您可能想使用内置的 native Promise.allSettled

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

As a fun fact, this answer below was prior art in adding that method to the language :]

一个有趣的事实是,下面的这个答案是将该方法添加到语言中的现有技术:]



Sure, you just need a reflect:

当然,你只需要一个reflect

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});

Or with ES5:

或者使用 ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

Or in your example:

或者在你的例子中:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});

回答by jib

Similar answer, but more idiomatic for ES6 perhaps:

类似的答案,但对于 ES6 来说可能更惯用:

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
<div id="div"></div>

Depending on the type(s) of values returned, errors can often be distinguished easily enough (e.g. use undefinedfor "don't care", typeoffor plain non-object values, result.message, result.toString().startsWith("Error:")etc.)

根据类型的值(一个或多个)返回时,往往可以区分错误很轻松了(如使用undefined了“不关心”,typeof对于普通的非对象值result.messageresult.toString().startsWith("Error:")等等)

回答by CertainPerformance

There is a finished proposalfor a function which can accomplish this natively, in vanilla Javascript: Promise.allSettled, which has made it to stage 4, is officialized in ES2020, and is implemented in all modern environments. It is very similar to the reflectfunction in this other answer. Here's an example, from the proposal page. Before, you would have had to do:

有一个可以在原生 Javascript 中完成此功能的完整提案Promise.allSettled,它已进入第 4 阶段,在 ES2020 中正式化,并在所有现代环境中实现。它与另一个答案中reflect功能非常相似。这是提案页面上的一个示例。以前,您必须执行以下操作:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Using Promise.allSettledinstead, the above will be equivalent to:

使用Promise.allSettled代替,上面将等效于:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Those using modern environments will be able to use this method without any libraries. In those, the following snippet should run without problems:

那些使用现代环境的人将能够在没有任何库的情况下使用这种方法。在这些中,以下代码段应该可以正常运行:

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b')
])
  .then(console.log);

Output:

输出:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]


For older browsers, there is a spec-compliant polyfill here.

对于旧的浏览器,有一个符合规范的填充工具在这里

回答by Kuba Wyrostek

I really like Benjamin's answer, and how he basically turns all promises into always-resolving-but-sometimes-with-error-as-a-result ones. :)
Here's my attempt at your request just in case you were looking for alternatives. This method simply treats errors as valid results, and is coded similar to Promise.allotherwise:

我真的很喜欢本杰明的回答,以及他如何基本上把所有的承诺变成总是解决但有时会出错的结果。:)
这是我应您要求的尝试,以防万一您正在寻找替代品。此方法只是将错误视为有效结果,并且编码与Promise.all其他方式类似:

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}

回答by martin770

var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

The Promise.allwill swallow any rejected promise and store the error in a variable, so it will return when all of the promises have resolved. Then you can re-throw the error out, or do whatever. In this way, I guess you would get out the last rejection instead of the first one.

Promise.all会吞噬任何拒绝承诺并存储错误的变量,所以当所有的承诺都得到解决,它将返回。然后你可以重新抛出错误,或者做任何事情。这样,我猜你会得到最后一个拒绝而不是第一个。

回答by user1016265

I had the same problem and have solved it in the following way:

我遇到了同样的问题,并通过以下方式解决了它:

const fetch = (url) => {
  return node-fetch(url)
    .then(result => result.json())
    .catch((e) => {
      return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
    });
};

tasks = [fetch(url1), fetch(url2) ....];

Promise.all(tasks).then(......)

In that case Promise.allwill wait for every Promise will come into resolvedor rejectedstate.

在那种情况下Promise.all将等待每个 Promise 进入resolvedrejected状态。

And having this solution we are "stopping catchexecution" in a non-blocking way. In fact, we're not stopping anything, we just returning back the Promisein a pending state which returns another Promisewhen it's resolved after the timeout.

有了这个解决方案,我们以catch非阻塞的方式“停止执行”。事实上,我们并没有停止任何事情,我们只是返回Promise处于挂起状态的 ,Promise当它在超时后解决时返回另一个。

回答by mpen

This should be consistent with how Q does it:

这应该与Q 的做法一致:

if(!Promise.allSettled) {
    Promise.allSettled = function (promises) {
        return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
            state: 'fulfilled',
            value: v,
        }), r => ({
            state: 'rejected',
            reason: r,
        }))));
    };
}

回答by NuOne

Instead of rejecting, resolve it with a object. You could do something like this when you are implementing promise

与其拒绝,不如用一个对象解决它。当你实现 promise 时,你可以做这样的事情

const promise = arg => {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          if(arg != 2)
            return resolve({success: true, data: arg});
          else
            throw new Error(arg)
        }catch(e){
          return resolve({success: false, error: e, data: arg})
        }
      }, 1000);
  })
}

Promise.all([1,2,3,4,5].map(e => promise(e))).then(d => console.log(d))

回答by Keith

Benjamin Gruenbaum answer is of course great,. But I can also see were Nathan Hagen point of view with the level of abstraction seem vague. Having short object properties like e & vdon't help either, but of course that could be changed.

Benjamin Gruenbaum 的回答当然很棒。但我也可以看出 Nathan Hagen 的观点与抽象层次似乎有些模糊。拥有简短的对象属性e & v也无济于事,但当然可以更改。

In Javascript there is standard Error object, called Error,. Ideally you always throw an instance / descendant of this. The advantage is that you can do instanceof Error, and you know something is an error.

在 Javascript 中有一个标准的 Error 对象,称为Error,。理想情况下,你总是抛出一个实例/它的后代。优点是你可以做instanceof Error,而且你知道有些事情是错误的。

So using this idea, here is my take on the problem.

所以使用这个想法,这是我对这个问题的看法。

Basically catch the error, if the error is not of type Error, wrap the error inside an Error object. The resulting array will have either resolved values, or Error objects you can check on.

基本上捕获错误,如果错误不是 Error 类型,则将错误包装在 Error 对象中。生成的数组将具有已解析的值或您可以检查的 Error 对象。

The instanceof inside the catch, is in case you use some external library that maybe did reject("error"), instead of reject(new Error("error")).

catch 中的 instanceof 是为了防止您使用一些可能已经使用的外部库reject("error"),而不是reject(new Error("error")).

Of course you could have promises were you resolve an error, but in that case it would most likely make sense to treat as an error anyway, like the last example shows.

当然,如果您解决了错误,您可以有承诺,但在这种情况下,无论如何将其视为错误很可能是有意义的,就像最后一个示例所示。

Another advantage of doing it this, array destructing is kept simple.

这样做的另一个好处是数组析构保持简单。

const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);

Instead of

代替

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }

You could argue that the !error1check is simpler than an instanceof, but your also having to destruct both v & e.

您可能会争辩说!error1检查比 instanceof 更简单,但您还必须同时销毁v & e.

function PromiseAllCatch(promises) {
  return Promise.all(promises.map(async m => {
    try {
      return await m;
    } catch(e) {
      if (e instanceof Error) return e;
      return new Error(e);
    }
  }));
}


async function test() {
  const ret = await PromiseAllCatch([
    (async () => "this is fine")(),
    (async () => {throw new Error("oops")})(),
    (async () => "this is ok")(),
    (async () => {throw "Still an error";})(),
    (async () => new Error("resolved Error"))(),
  ]);
  console.log(ret);
  console.log(ret.map(r =>
    r instanceof Error ? "error" : "ok"
    ).join(" : ")); 
}

test();