【问题标题】:Stop other promises when Promise.all() rejects当 Promise.all() 拒绝时停止其他承诺
【发布时间】:2018-07-23 14:00:38
【问题描述】:

虽然所有关于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() 抛出!有人可以解释为什么吗?当其他承诺被拒绝时,我能做些什么来阻止其他承诺?

【问题讨论】:

  • Promise 没有魔法取消。如果你想要这种行为,你必须将setTimeout 的返回值保存在某处并调用clearTimeout
  • 但这只是一个例子,在实际用例中,我的承诺不会仅在超时时执行。除了 Node 的 process.exit() 之外,没有通用的方法来阻止 Promise 吗?
  • 不能以你想要的方式取消承诺。这是一个众所周知的限制。像 rxjs 这样的可观察库提供了解决这个问题的替代方案。
  • 为什么在Promise 中使用setTimeout?您可能会发现此演示文稿很有用:youtu.be/cCOL7MC4Pl0
  • @quezak:这个想法和clearTimeout一样。首先,您需要一种停止任务的方法。在这一点上,Promise 并没有真正涉及。

标签: javascript promise es6-promise


【解决方案1】:

如cmets中所述promises cannot be canceled.

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

【讨论】:

  • Promise 不支持取消。但是,有可用于提供取消的 Abort Controller API。 Polyfill 库可用于为节点和旧版浏览器提供此功能。
【解决方案2】:

Promises/A+ specification 中不包含取消承诺。

但是,一些 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
    });
&lt;script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"&gt;&lt;/script&gt;

请注意,即使 promise3 被取消,它的 setTimeout 回调仍然会被调用。但它不会触发thencatch 回调。就好像这个承诺永远不会成为一个决议......永远。

如果您还想停止触发计时器事件,那么这与 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
    });
&lt;script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"&gt;&lt;/script&gt;

【讨论】:

    【解决方案3】:

    如果一个promise不再可达,那么进程将退出,因此可以创建一个小助手来实现这一点

    
    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 承诺永远不会解决,也会记录完成然后退出。

    【讨论】:

      猜你喜欢
      • 2017-06-19
      • 2021-09-16
      • 2019-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-11-25
      • 2016-08-23
      • 2018-11-20
      • 1970-01-01
      相关资源
      最近更新 更多