当 Promise.all() 拒绝时停止其他 Promise

问题描述 投票:0回答:4

虽然关于

Promise.all
的所有问题都集中在如何等待所有承诺,但我想走另一条路——当任何一个承诺失败时,停止其他承诺,甚至停止整个脚本。

这里有一个简短的例子来说明:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'resolve1');
}).then(a => { console.log('then1'); return a; });

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 2000, 'reject2');
}).then(a => { console.log('then2'); return a; });

const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'resolve3');
}).then(a => { console.log('then3'); return a; });

Promise.all([promise1, promise2, promise3])
  .then(values => { console.log('then', values); })
  .catch(err => { console.log('catch', err); throw err; });

// results:
// > "then1"
// > "catch" "reject2"
// > "then3"    <------- WHY?

脚本继续解析

promise3
,即使最后的
all(...).catch()
抛出!有人可以解释为什么吗?当其他承诺被拒绝时,我该怎么做才能阻止它们?

javascript promise es6-promise
4个回答
14
投票

取消承诺不包含在Promises/A+ 规范中

但是,一些 Promise 库有这样的取消扩展。以bluebird为例:

Promise.config({ cancellation: true }); // <-- enables this non-standard feature

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'resolve1');
}).then(a => { console.log('then1'); return a; });

const promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 2000, 'reject2');
}).then(a => { console.log('then2'); return a; });

const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, 'resolve3');
}).then(a => { console.log('then3'); return a; });

const promises = [promise1, promise2, promise3];

Promise.all(promises)
    .then(values => { 
        console.log('then', values); 
    })
    .catch(err => { 
        console.log('catch', err); 
        promises.forEach(p => p.cancel()); // <--- Does not work with standard promises
    });
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"></script>

注意,即使promise3被取消,它的

setTimeout
回调仍然会被调用。但它不会触发
then
catch
回调。就好像这个承诺永远不会得到解决......永远。

如果您还想停止触发计时器事件,那么这与 Promise 无关,可以通过

clearTimeout
来完成。 Bluebird 在 Promise 构造函数中公开了一个
onCancel
回调函数,当 Promise 被取消时它将调用该函数。所以你可以用它来删除计时器事件:

Promise.config({ cancellation: true }); // <-- enables this non-standard feature

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'resolve1');
}).then(a => { console.log('then1'); return a; });

const promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 2000, 'reject2');
}).then(a => { console.log('then2'); return a; });

const promise3 = new Promise((resolve, reject, onCancel) => { // Third argument (non-standard)
    var timer = setTimeout(resolve, 3000, 'resolve3');
    onCancel(_ => {
        clearTimeout(timer);
        console.log('cancelled 3');
    });
}).then(a => { console.log('then3'); return a; });

const promises = [promise1, promise2, promise3];

Promise.all(promises)
    .then(values => { 
        console.log('then', values); 
    })
    .catch(err => { 
        console.log('catch', err); 
        promises.forEach(p => p.cancel()); // <--- Does not work with standard promises
    });
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"></script>


0
投票

如评论中所述承诺无法取消。

您需要使用第三方 Promise 库或 rxjs observables。


0
投票

如果承诺不再可达,则进程将退出,因此可以创建一个小助手来实现此目的


function timeoutWhen(promises, bail) {
  const pending = promises
    .map(promise => Promise.race([ bail, promise ]))
  return Promise.all(pending)
}


const never = new Promise(() => {})
const done = Promise.resolve()

const cancel = new Promise(ok => setTimeout(ok, 1000))

timeoutWhen([ never, done ], cancel)
  .then(() => {
    console.log('done')
  })

将记录完成然后退出,即使

never
承诺永远不会解决。


0
投票

您可以使用

AbortController
实现无需外部库的取消,但您的 Promise 的编写方式必须稍有不同。

有了这样的承诺:

const controller = new AbortController();
const { signal } = controller;

const promise1 = new Promise((resolve, reject) => {
  const timeoutId = setTimeout(resolve, 1000, 'resolve1');
  signal.addEventListener("abort", () => {
    clearTimeout(timeoutId);
    reject(signal.reason);
  });
}).then(a => { console.log('then1'); return a; });

const promise2 = new Promise((resolve, reject) => {
  const timeoutId = setTimeout(reject, 2000, 'reject2');
  signal.addEventListener("abort", () => {
    clearTimeout(timeoutId);
    reject(signal.reason);
  });
}).then(a => { console.log('then2'); return a; });

const promise3 = new Promise((resolve, reject) => {
  const timeoutId = setTimeout(resolve, 3000, 'resolve3');
  signal.addEventListener("abort", () => {
    clearTimeout(timeoutId);
    reject(signal.reason);
  });
}).then(a => { console.log('then3'); return a; });

您应该在开始执行 Promise 之前验证信号的状态,但为了更简洁,我省略了它。

然后您可以创建以下函数:

/**
 *
 * @param {Array<Promise>} promises
 * @param {AbortController} controller
 */
const promiseAllWithCancellation = async (promises, controller) => {
  if (!controller) throw TypeError("AbortController is required");
  try {
    return await Promise.all(promises);
  } catch (error) {
    controller.abort();
    throw error;
  }
};

那么你的执行将是:

promiseAllWithCancellation([promise1, promise2, promise3], controller)
  .then(console.log, (error) => {
    if (error instanceof Error) {
      console.error(`[${error.name}]: ${error.message}`);
    } else {
      console.log(`[Error]: ${error}`);
    }
  });
// results:
// > "then1"
// > "[Error]: reject2"

如果您想更加努力,您可以将

promiseAllWithCancellation
添加到
Promise
原型或实现自定义
CancellablePromise
类,继承自
Promise

© www.soinside.com 2019 - 2024. All rights reserved.