等到所有ES6承诺完成,甚至拒绝承诺

时间:2015-07-15 07:53:56

标签: javascript promise es6-promise

假设我有一组提出网络请求的承诺,其中一个会失败:

// 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   

假设我想等到所有这些都结束了,不管是否有失败。对于我可以没有的资源,可能存在网络错误,但是如果我可以获得,我希望在继续之前。我想优雅地处理网络故障。

由于Promises.all没有为此留下任何空间,在不使用promises库的情况下,处理此问题的建议模式是什么?

18 个答案:

答案 0 :(得分:202)

类似的答案,但对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>"};
&#13;
<div id="div"></div>
&#13;
&#13;
&#13;

根据返回的值的类型,通常可以很容易地区分错误(例如,使用undefined来关注&#34;不关心&#34;,typeof普通的非对象值,result.messageresult.toString().startsWith("Error:")等。)

答案 1 :(得分:57)

本杰明的回答为解决这个问题提供了很好的抽象,但我希望得到一个不那么抽象的解决方案。解决此问题的明确方法是简单地在内部promises上调用.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));

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

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

然后你可以做

> 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 } ]

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

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

所以现在你可以这样做:

> 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!' ]

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

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

现在看起来像

> 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!' ]

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

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

例如,你可能有这个:

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

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

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

没有办法告诉哪一个是致命的,哪个不是。如果这很重要,那么您将要执行和界面来跟踪它是否成功(reflect执行)。

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

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

就我而言,我不需要知道错误或错误 - 我只关心我是否有价值。我将让生成promise的函数担心记录特定错误。

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

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

我希望我的高级功能安全地失败,而不用担心其依赖性失败原因的细节,而且当我必须做出权衡时我也更喜欢KISS干 - 这最终是我选择不使用{的原因{1}}。

答案 2 :(得分:9)

我真的很喜欢本杰明的答案,以及他如何将所有的承诺变成永远解决但有时与错误相关的结果。 :)
这是我尝试你的请求,以防万一你正在寻找替代方案。此方法只是将错误视为有效结果,并且编码类似于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);
  });
}

答案 3 :(得分:4)

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

Promise.all将吞下任何被拒绝的承诺并将错误存储在变量中,因此当所有承诺都已解决时它将返回。然后你可以重新抛出错误,或做任何事情。通过这种方式,我猜你会得到最后一次拒绝而不是第一次拒绝。

答案 4 :(得分:4)

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

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(......)

在这种情况下,Promise.all将等待每个承诺进入resolvedrejected州。

有了这个解决方案,我们就会停止catch执行&#34;以非阻塞的方式。事实上,我们并未停止任何操作,我们只是将Promise返回到待处理状态,并在超时后解析另一个Promise

答案 5 :(得分:2)

这应与how Q does it

一致
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,
        }))));
    };
}

答案 6 :(得分:2)

有一个proposal可以通过原生Javascript Promise.allSettled来原生完成此功能。它目前处于第3阶段,很有可能将其纳入正式规范。它与this other answer中的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');

相反,使用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');

一旦这成为规范的一部分并且浏览器实现了它,您就可以在没有任何库的现代浏览器中使用它。

答案 7 :(得分:2)

Benjamin Gruenbaum的答案当然很棒。但我也可以看到,内森·哈根(Nathan Hagen)的观点与抽象水平似乎很模糊。拥有e & v之类的短对象属性也无济于事,但是当然可以更改。

在Javascript中,有一个标准的错误对象,称为Error。理想情况下,您总是抛出一个实例/其后代。优点是您可以执行instanceof Error,并且知道某些错误。

因此,使用这个想法,我来解决这个问题。

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

如果您使用的外部库可能是reject("error")而不是reject(new Error("error")),那么catch内的instanceof就可以了。

您当然可以承诺,如果您解决了一个错误,但是在那种情况下,无论如何都应将其视为错误,就像最后一个示例所示。

这样做的另一个好处是,数组销毁保持简单。

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

代替

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

您可能会说!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();

答案 8 :(得分:1)

而不是拒绝,而是通过一个对象来解决。 当您实现诺言时,您可以做类似的事情

    JSONObject requestJson = new JSONObject(streams).toString();
    HttpEntity<StreamsForCodecs> requestEntity = new HttpEntity<>(requestJson , headers);

答案 9 :(得分:0)

不幸的是,我只是想要一个能够完全复制ES2020行为的polyfill,因为我被锁定的节点版本早于12.9(出现Promise.allSettled时)。因此,值得的是,这是我的版本:

const settle = (promise) => (promise instanceof Promise) ?
  promise.then(val => ({ value: val, status: "fulfilled" }),
               err => ({ reason: err, status: "rejected" })) :
  { value: promise, status: 'fulfilled' };

const allSettled = async (parr) => Promise.all(parr.map(settle));

与ES版本一样,它处理承诺值和非承诺值的混合数组。它会将与本机版本相同的{ status, value/reason }对象数组返回。

答案 10 :(得分:0)

我认为以下内容提供了一种稍有不同的方法...比较fn_fast_fail()fn_slow_fail() ...虽然后者不会失败...您可以检查其中一个还是两个abError的{​​{1}}和throw的实例,如果您希望它到达Error块(例如{{1 }})。参见jsfiddle

catch

答案 11 :(得分:0)

这是我的自定义settledPromiseAll()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

Promise.all

相比
  • 如果所有的诺言都得到了解决,它的表现将与标准诺言完全一样。

  • 如果一个或多个诺言被拒绝,它会返回第一个被拒绝的诺言,与标准诺言几乎一样,但它会等待所有诺言得到解决/拒绝。

为了勇敢,我们可以更改Promise.all()

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

小心。通常,我们永远不会更改内置函数,因为它可能会破坏其他不相关的JS库或与将来对JS标准的更改发生冲突。

我的settledPromiseallPromise.all向后兼容,并扩展了其功能。

正在开发标准的人们-为什么不将其包括在新的Promise标准中?

答案 12 :(得分:-1)

自ES5以来,我一直在使用以下代码。

Promise.wait = function(promiseQueue){
    if( !Array.isArray(promiseQueue) ){
        return Promise.reject('Given parameter is not an array!');
    }

    if( promiseQueue.length === 0 ){
        return Promise.resolve([]);
    }

    return new Promise((resolve, reject) =>{
        let _pQueue=[], _rQueue=[], _readyCount=false;
        promiseQueue.forEach((_promise, idx) =>{
            // Create a status info object
            _rQueue.push({rejected:false, seq:idx, result:null});
            _pQueue.push(Promise.resolve(_promise));
        });

        _pQueue.forEach((_promise, idx)=>{
            let item = _rQueue[idx];
            _promise.then(
                (result)=>{
                    item.resolved = true;
                    item.result = result;
                },
                (error)=>{
                    item.resolved = false;
                    item.result = error;
                }
            ).then(()=>{
                _readyCount++;

                if ( _rQueue.length === _readyCount ) {
                    let result = true;
                    _rQueue.forEach((item)=>{result=result&&item.resolved;});
                    (result?resolve:reject)(_rQueue);
                }
            });
        });
    });
};

用法签名就像Promise.all。主要区别在于Promise.wait将等待所有诺言完成工作。

答案 13 :(得分:-1)

我知道这个问题有很多答案,而且我敢肯定(如果不是全部)正确。 但是,我很难理解这些答案的逻辑/流程。

因此,我查看了Promise.all()上的原始实现,并尝试模仿该逻辑-除了在一个Promise失败的情况下不停止执行之外。

  public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]>
  {
    let promise: Promise<{ data: any, isSuccess: boolean }[]>;

    if (promisesList.length)
    {
      const result: { data: any, isSuccess: boolean }[] = [];
      let count: number = 0;

      promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) =>
      {
        promisesList.forEach((currentPromise: Promise<any>, index: number) =>
        {
          currentPromise.then(
            (data) => // Success
            {
              result[index] = { data, isSuccess: true };
              if (promisesList.length <= ++count) { resolve(result); }
            },
            (data) => // Error
            {
              result[index] = { data, isSuccess: false };
              if (promisesList.length <= ++count) { resolve(result); }
            });
        });
      });
    }
    else
    {
      promise = Promise.resolve([]);
    }

    return promise;
  }

说明:
-循环输入promisesList并执行每个Promise。
-无论Promise是否解决或拒绝:根据result将Promise的结果保存在index数组中。还保存解析/拒绝状态(isSuccess)。
-完成所有承诺后,返回一个带有其他所有结果的承诺。

使用示例:

const p1 = Promise.resolve("OK");
const p2 = Promise.reject(new Error(":-("));
const p3 = Promise.resolve(1000);

promiseExecuteAll([p1, p2, p3]).then((data) => {
  data.forEach(value => console.log(`${ value.isSuccess ? 'Resolve' : 'Reject' } >> ${ value.data }`));
});

/* Output: 
Resolve >> OK
Reject >> :-(
Resolve >> 1000
*/

答案 14 :(得分:-1)

您可以通过同步执行程序nsynjs按顺序执行逻辑。它将在每个promise上暂停,等待解析/拒绝,并将resolve的结果分配给data属性,或者抛出异常(为了处理你需要try / catch块)。这是一个例子:

function synchronousCode() {
    function myFetch(url) {
        try {
            return window.fetch(url).data;
        }
        catch (e) {
            return {status: 'failed:'+e};
        };
    };
    var arr=[
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"),
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"),
        myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js")
    ];
    
    console.log('array is ready:',arr[0].status,arr[1].status,arr[2].status);
};

nsynjs.run(synchronousCode,{},function(){
    console.log('done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

答案 15 :(得分:-1)

我愿意:

var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];

Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never executed

答案 16 :(得分:-1)

Promise.all使用现代的async/await方法

const promise1 = //...
const promise2 = //...

const data = await Promise.all([promise1, promise2])

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]

答案 17 :(得分:-3)

我不知道您使用的是哪个承诺库,但大多数都有类似allSettled的内容。

编辑:好的,因为你想使用没有外部库的普通ES6,没有这样的方法。

换句话说:您必须手动循环您的承诺,并在所有承诺结算后立即解决新的组合承诺。