【问题标题】:How can I break out of this Promise for loop?我怎样才能摆脱这个 Promise for 循环?
【发布时间】:2020-01-05 08:44:22
【问题描述】:

编辑:我知道如何使用 async/await 让它工作,但不幸的是,我不能使用它,因为它没有得到广泛支持(尤其是 iOS 10、Internet Explorer 等)。所以我需要一个只有承诺的方式。

我对 Promises 很陌生,所以我被卡住了。我有这个 for 循环,只要 i 等于 2(例如),我就想打破它。我已经尝试放置reject,但它仍然持续到i<10。我希望它在满足我的条件时停止。

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
  p = p.then(_ => new Promise((resolve, reject) => {
    setTimeout(function() {
      console.log(i);
      if (i == 2) {
        reject("BREAK HERE");
      } else {
        resolve();
      }
    }, Math.random() * 1000)
  })).catch(err => {
    console.log(err);
  });
}

当前输出:

0
1
2
BREAK HERE
3
4
5
6
7
8
9

我希望它只是:

0
1
2
BREAK HERE

【问题讨论】:

  • .catch() 正在阻止它实际失败。
  • 您的catch 不会传播错误
  • @TKoL 然后它在控制台中打印一个丑陋的错误: (node:66142) UnhandledPromiseRejectionWarning: undefined (node:66142) UnhandledPromiseRejectionWarning: UnhandledPromiseRejectionWarning: Unhandled Promise RejectionWarning。此错误源于在没有 catch 块的情况下抛出异步函数内部,或拒绝未使用 .catch() 处理的承诺。 (拒绝 ID:1)(节点:66142)[DEP0018] DeprecationWarning:不推荐使用未处理的承诺拒绝。将来,未处理的 Promise 拒绝将使用非零退出代码终止 Node.js 进程。
  • 你能解释一下你想用这段代码实现什么吗?你在这里的目标是什么? (我的意思不是“打破这个承诺循环”的意思,而是更抽象的意思。)就目前而言,您的代码看起来非常复杂。
  • 我特别是。想知道你的p = Promise.resolve() 部分。为什么定义一个总是成功的承诺只是为了在 for 循环中附加一个成功处理程序?然后你创造了另一个承诺。为什么?

标签: javascript node.js ecmascript-6 promise es6-promise


【解决方案1】:

只要不解决,序列就会停止

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
  p = p.then(_ => new Promise((resolve, reject) => {
    setTimeout(function() {
      console.log("i",i);
      if (i == 2) {
        console.log("break here")
        
      } else {
        resolve();
      }
    }, Math.random() * 1000)
  })).catch(err => {
    console.log("err",err);
  });
}

但是由于这段代码本身打算对异步调用进行排序,因此应该考虑这样做:

// repeated call
next(
  0/*state*/, 
  i=>i<2/*match*/, 
  i=>i+1/*increment*/,     
  asyncCallPromisefied/*promise|promise[]*/, 
  [0]/*args*/
)
.catch(console.log)


// traverse array
next(
  0/*state*/, 
  i => i < 6/*match*/, 
  i => i + 1/*increment*/,
  asyncChain()/*promise|promise[]*/, 
  [0]/*args*/,
  0/*[promise[] index]*/
)
.catch(console.log)


// helper
function next(state, match, increment, action, args, pointer = 0/**inactive**/){
  if(match(state))
  if(Array.isArray(action)){
     
     return pointer < action.length
      ? action[pointer](...args)
      .then(i => next(
        increment(state), 
        match, 
        increment, 
        action, 
        [i],
        pointer + 1
      ))
      : undefined;
  } else {
    return action(...args)
    .then(i => next(
      increment(state), 
      match, 
      increment, 
      action, 
      [i]
    ))
  }
}



// wrapper
function asyncCall(i, callback){ 
  return setTimeout(() => {
    console.log(i == 2 ? 'break here': i);
    callback(null, ++i);
  }, Math.random() * 1000)
}

function asyncCallPromisefied(i){
  return new Promise(function(resolve, reject){
    asyncCall(i, function(err, args){
      if(err){
        reject(err)
      } else {
        resolve(args)
      }
    })
  })
}

function asyncChain(){return [
  function(i){console.log("chain",i);return Promise.resolve(i)},
  function(i){console.log("chain",i);return Promise.resolve(i)},
 function(i){console.log("chain",i);return Promise.resolve(i)},
  asyncCallPromisefied,
  function(i){console.log("chain",i);return Promise.resolve(i)},
]}

【讨论】:

  • 这可能是内存泄漏,但如果您多次执行此操作。
  • @PranoyC for 循环仍然没有意义。无需创建 p = Promise.resolve() 。此外,未解决的 Promise 将永远处于等待状态,占用内存。
  • @k0pernikus p 被限定在 for 循环内,并且会失败,因此会收集垃圾 - 承诺链也是如此。但我同意这是进行后续调用的不好方法之一 - 我会清理它。
【解决方案2】:

删除catch,否则,错误将被捕获并处理。要记录而不是错误,请使用console.log

try {

  for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(_ => new Promise((resolve, reject) => {
      setTimeout(function() {
        console.log(i);
        if (i == 2) {
          reject(console.log("BREAK HERE"));
        } else {
          resolve();
        }
      }, Math.random() * 1000)
    }));
  }

} catch (e) {}

【讨论】:

  • 这种工作,但在控制台打印一个丑陋的错误:(node:66142) UnhandledPromiseRejectionWarning: undefined (node:66142) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:66142) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
【解决方案3】:

据我了解您的问题,您希望一个接一个地按顺序执行。然后,您希望在第一个承诺失败后立即中止。

为了确保执行顺序,您必须在 for 循环中这样做,尽管您失去了并发运行时的实际好处。此代码将按顺序运行:

const delay = async () => new Promise((resolve) => setTimeout(() => resolve(), Math.random() * 1e3);

const myUpload = async (file) => {
    await delay();

    if (file === 2) {
        return Promise.reject(`COULD NOT UPLOAD ${file}`);
    } else {
        console.log("uploaded file", file);

        return;
    }

};

const uploadFilesSequentually = async (files) => {
    for (const file of files) {
        await myUpload(file); // this will create, and wait for the promise to finish or fail, only then the for loop continues
    }
};

如果您不关心执行顺序并允许并发,我会这样做:

const promises = myFiles.map(myUpload); // all promises are now pending and will resolve in different orders
Promise.all(promises) // returns its own promise then resolves once all of them have succeded or is rejected as soon as one is rejected
    .then((result) => console.log("everything uploaded"))
    .catch(console.error);

Promise.all 的结果将保持结果的顺序。所以即使第三个承诺在第一个之前解决,结果也会是[resultPromise1, resultPrimise2, resultPromise3, ... ]

【讨论】:

  • 不幸的是,我不能使用async/await,因为它没有得到广泛的支持。我的很多客户都使用旧版浏览器(IE、iOS 10),并且不支持。
  • @PranoyC 您可以为旧版浏览器使用 babel 和 polyfill。如果我找到时间,我会尝试添加一个非异步等待答案。但它的想法并没有改变。
  • 谢谢,我能够通过简单地“不是resolve 并且序列停止”来找出解决方案。不需要rejectstackoverflow.com/a/57763342/1634905
【解决方案4】:

目前,我最终在没有 Promise 的情况下使用了不同的方法来实现这一目标:

var i=0;

function fetchNext(){
    if (i==2) {
        console.log("BREAK HERE");
    } else {
        setTimeout(function () {
            console.log(i);
            i++;
            fetchNext();
        }, Math.random() * 1000);
    }
}

fetchNext();

【讨论】:

    【解决方案5】:

    把循环中的.catch去掉,之后再处理。

    let p = Promise.resolve();
    for (let i = 0; i < 10; i++) {
      p = p.then(_ => new Promise((resolve, reject) => {
        setTimeout(function() {
          console.log(i);
          if (i == 2) {
            reject("BREAK HERE");
          } else {
            resolve();
          }
        }, Math.random() * 1000)
      }));
    }
    
    p.catch(err => {
        console.log(err);
      })
    

    【讨论】:

      【解决方案6】:

      在任何情况下,您的代码将始终执行 10 次,因为 setTimeout 是异步操作,并且直到满足您的条件 reject("BREAK HERE") 时,promise 已经被链接了十次。

      这是你可以做的,虽然它看起来很乱:(

      for (let i = 0, p = Promise.resolve(); i < 10; i++) {
        p = p.then(data => new Promise((resolve, reject) => {
          setTimeout(function() {
            if(data && data.isRejected){
              resolve({ isRejected: true })
              return;
            }
            console.log(i);
            if (i == 2) {
              reject("BREAK HERE");
            } else {
              resolve({});
            }
          }, Math.random() * 1000)
        })).catch(err => {
          console.log(err);
          /* add this line */
          return { isRejected: true };
        });
      }

      从 catch 中返回一个对象(而不是 throw)会将 promise 标记为已解决。更多docs 这将为您提供所需的结果

      【讨论】:

      • 解释很好,但你的代码输出不是 OP 要求的:BREAK HERE 应该只记录一次......
      • 这不是我想要的。我不希望它多次打印BREAK HERE
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-18
      • 1970-01-01
      • 2021-01-27
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      • 1970-01-01
      相关资源
      最近更新 更多