Break a Promise chain inside for-loop

2019-07-07 17:53发布

问题:

I am working on a promise chain that was inspired by this answer: https://stackoverflow.com/a/44955506/7485805

I want to break this for loop in order to properly handle the rejection of the chain. I just figured that I can't use break inside the .catch method of the chain.

Here's my code if it helps:

function pro (arr) {
  let chain = Promise.resolve();
  const self = {req: {}, res: {}};
  const length = arr.length;

  return new Promise((resolve, reject) => {
    for(let i=0; i<length; i++){
      chain = chain
          .then(() => arr[i].call(self) )
          .then(() => {
            if(i === (length - 1) )
              resolve();
          })
          .catch(e => {
            reject(e);
          })
    }
  })
  .then(() => {
    return self
  })
  .catch(e => {
    throw new Error (e);
  })

}

const x = function () {
  const self = this;
  return new Promise(resolve => {
    self.req = {key: "value"}
    resolve();
  })  
}

const y =  function () {
  const self = this;
  return new Promise((resolve, reject) => {
    console.log(self);
    reject();
  })
}

const z = function () {
  const self = this;
  return new Promise((resolve, reject) => {
    console.log('failed');
  })
}



pro([x, y, z])
.then((self) => {
  console.log('final',self);
})
.catch(e => {
  console.log('error', e);
})

x, y, z are three functions chained together in function pro While, x resolves successfully, y is executed but gets rejected.

I want to stop the execution of z since it's pointless to continue and may produce error in actual code.

Also, if someone can recommend me a better version for this piece of code:

.then(() => {
  if(i === (length - 1) )
    resolve();
})

Note: I cannot use await since this code will be executed server-side and use of await may block other incoming requests.

回答1:

It is much easier to do with the async/await syntax:

async function pro(arr) {
    const self = {req: {}, res: {}};
    for(const f of arr) await f.call(self);
    return self;
}

async function pro(arr) {
    const self = {req: {}, res: {}};
    for(const f of arr) await f.call(self);
    return self;
}

const x = function () {
  const self = this;
  return new Promise(resolve => {
    self.req = {key: "value"}
    resolve();
  })  
}

const y =  function () {
  const self = this;
  return new Promise((resolve, reject) => {
    console.log(self);
    reject("y failed");
  })
}

const z = function () {
  const self = this;
  return new Promise((resolve, reject) => {
  	console.log('failed');
  })
}

pro([x, y, z]).then((self) => {
  console.log('final',self);
})
.catch(e => {
  console.log('error', e);
});



回答2:

A few things: when you construct your promise chain inside of a for loop, that's all that happens: the chain is constructed. The .then execution will happen at the earliest in the next event loop. I'll try to illustrate:

var promiseChain = functionReturningPromise();
for(var i=0;i<3;i++){
  promiseChain = promiseChain.then(x=> {
    return anotherPromiseFunction(x);
  });
}

Depending on what functionReturningPromise actually does, something may have already happened at this point...or maybe not. For example we may have started a fetch, or maybe started up a WebWorker. However if we had a setTimeout nested inside of the first Promise, then all we did was put the setTimeout callback into the queue for the next cycle of the event loop. But guaranteed 100%, no .then functions have run yet. That comes later, in the next event loop.

So next event loop comes, and the promise has resolved. That means the next .then will get run. Let's say it fails. At that point, because we chained the promises (promiseChain = promiseChain.then), we immediately skip to the first .catch (or .then with a second parameter) in the chain, and all intervening .thens get completely skipped without being executed. Or if there are no catches, then this promise chain is done. No break necessary; it's just how Promises work.

So if you just add a .catch at the very end of the chain, you're good.

About this "event loop" thing: I really recommend watching Jake Archibald: In The Loop, from JSConf.Asia 2018.

Also about await...Sounds as if there is some confusion about how it works. You can only use await inside an async function, so you can never completely block thread execution with a single await. It works just like chaining .thens, just syntactic sugar. So @trincot is definitely right, you're going to be much happier with that syntax.



回答3:

Here's an alternative answer that just executes the promises in succession unless a promise was rejected which is then stopped. This doesn't use await but it should give you a general idea how it could be done without it but this code was also written very quickly so it's not the most optimised code.

const x = function() {
  return new Promise(resolve => {
    resolve('it resolved ma!');
  });
};

const y = function() {
  const self = this;
  return new Promise((resolve, reject) => {
    reject("reject");
  });
};

const z = function() {
  const self = this;
  return new Promise((resolve, reject) => {
    resolve("never gets executed");
  });
};

function runPromises(promises) {
  const results = [];
  let count = 0;
  const executePromise = i => {
    count++;
    return promises[i]()
      .then((response) => {
        results.push(response);
        if (count !== promises.length) {
          executePromise(count);
        }
      })
      .catch((e) => {
        results.push(e);
        console.log("stop right now, thank you very much");
      });
  };
  if (Array.isArray(promises)) {
    executePromise(count);
  }
  return results;
}

const results = runPromises([x, y, z]);
console.log(results);