javascript - 如何解决递归异步 promise ?

标签 javascript asynchronous recursion promise es6-promise

我在玩弄 promises,我在处理异步递归 promise 时遇到了麻烦。

场景是一位运动员开始跑 100 米,我需要定期检查他们是否跑完了,一旦他们跑完了,打印他们的时间。

编辑以澄清:

在现实世界中,运动员在服务器上运行。 startRunning 涉及对服务器进行 ajax 调用。 checkIsFinished 还涉及对服务器进行 ajax 调用。下面的代码试图模仿它。代码中的时间和距离是硬编码的,目的是让事情尽可能简单。抱歉没有说清楚。

结束编辑

我希望能够写出以下内容

startRunning()
  .then(checkIsFinished)
  .then(printTime)
  .catch(handleError)

在哪里

var intervalID;
var startRunning = function () {
  var athlete = {
    timeTaken: 0,
    distanceTravelled: 0
  };
  var updateAthlete = function () {
    athlete.distanceTravelled += 25;
    athlete.timeTaken += 2.5;
    console.log("updated athlete", athlete)
  }

  intervalID = setInterval(updateAthlete, 2500);

  return new Promise(function (resolve, reject) {
    setTimeout(resolve.bind(null, athlete), 2000);
  })
};

var checkIsFinished = function (athlete) {
  return new Promise(function (resolve, reject) {
    if (athlete.distanceTravelled >= 100) {
      clearInterval(intervalID);
      console.log("finished");
      resolve(athlete);

    } else {
      console.log("not finished yet, check again in a bit");
      setTimeout(checkIsFinished.bind(null, athlete), 1000);
    }    
  });
};

var printTime = function (athlete) {
  console.log('printing time', athlete.timeTaken);
};

var handleError = function (e) { console.log(e); };

我可以看到第一次 checkIsFinished 时创建的 promise 从未得到解决。我如何确保该 promise 得到解决,以便调用 printTime

代替

resolve(athlete);

我能做到

Promise.resolve(athlete).then(printTime);

但如果可能的话,我想避免这种情况,我真的很想能够写

startRunning()
  .then(checkIsFinished)
  .then(printTime)
  .catch(handleError)

最佳答案

错误在于您正在传递一个函数,该函数返回一个 promise 给 setTimeout。这个 promise 消失在以太中。创可贴修复可能是递归执行函数:

var checkIsFinished = function (athlete) {
  return new Promise(function executor(resolve) {
    if (athlete.distanceTravelled >= 100) {
      clearInterval(intervalID);
      console.log("finished");
      resolve(athlete);
    } else {
      console.log("not finished yet, check again in a bit");
      setTimeout(executor.bind(null, resolve), 1000);
    }    
  });
};

但是嗯。我认为这是一个很好的例子,说明为什么应该避免 promise-constructor anti-pattern (因为混合使用 promise 代码和非 promise 代码不可避免地会导致这样的错误)。

我遵循的避免此类错误的最佳做法:

  1. 只处理返回 promise 的异步函数。
  2. 当一个人不返回 promise 时,用 promise 构造函数包装它。
  3. 尽可能窄地(用尽可能少的代码)包装它。
  4. 不要将 promise 构造函数用于任何其他用途。

在此之后,我发现代码更容易推理,更难出错,因为一切都遵循相同的模式。

将此应用于您的示例让我来到这里(为简洁起见,我使用 es6 箭头函数。它们在 Firefox 和 Chrome 45 中工作):

var console = { log: msg => div.innerHTML += msg + "<br>",
                error: e => console.log(e +", "+ e.lineNumber) };

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));

var startRunning = () => {
  var athlete = {
    timeTaken: 0,
    distanceTravelled: 0,
    intervalID: setInterval(() => {
      athlete.distanceTravelled += 25;
      athlete.timeTaken += 2.5;
      console.log("updated athlete ");
    }, 2500)
  };
  return wait(2000).then(() => athlete);
};

var checkIsFinished = athlete => {
  if (athlete.distanceTravelled < 100) {
    console.log("not finished yet, check again in a bit");
    return wait(1000).then(() => checkIsFinished(athlete));
  }
  clearInterval(athlete.intervalID);
  console.log("finished");
  return athlete;
};

startRunning()
  .then(checkIsFinished)
  .then(athlete => console.log('printing time: ' + athlete.timeTaken))
  .catch(console.error);
<div id="div"></div>

请注意,checkIsFinished 返回运动员或 promise 。这在这里很好,因为 .then 函数会自动提升您传递给 promise 的函数的返回值。如果您将在其他情况下调用 checkIsFinished,您可能想要自己进行促销,使用 return Promise.resolve(athlete); 而不是 return athlete ;

根据 Amit 的评论进行编辑:

对于非递归答案,用这个助手替换整个 checkIsFinished 函数:

var waitUntil = (func, ms) => new Promise((resolve, reject) => {
  var interval = setInterval(() => {
    try { func() && resolve(clearInterval(interval)); } catch (e) { reject(e); }
  }, ms);
});

然后这样做:

var athlete;
startRunning()
  .then(result => (athlete = result))
  .then(() => waitUntil(() => athlete.distanceTravelled >= 100, 1000))
  .then(() => {
    console.log('finished. printing time: ' + athlete.timeTaken);
    clearInterval(athlete.intervalID);
  })
  .catch(console.error);

var console = { log: msg => div.innerHTML += msg + "<br>",
                error: e => console.log(e +", "+ e.lineNumber) };

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));

var waitUntil = (func, ms) => new Promise((resolve, reject) => {
  var interval = setInterval(() => {
    try { func() && resolve(clearInterval(interval)); } catch (e) { reject(e); }
  }, ms);
});

var startRunning = () => {
  var athlete = {
    timeTaken: 0,
    distanceTravelled: 0,
    intervalID: setInterval(() => {
      athlete.distanceTravelled += 25;
      athlete.timeTaken += 2.5;
      console.log("updated athlete ");
    }, 2500)
  };
  return wait(2000).then(() => athlete);
};

var athlete;
startRunning()
  .then(result => (athlete = result))
  .then(() => waitUntil(() => athlete.distanceTravelled >= 100, 1000))
  .then(() => {
    console.log('finished. printing time: ' + athlete.timeTaken);
    clearInterval(athlete.intervalID);
  })
  .catch(console.error);
<div id="div"></div>

关于javascript - 如何解决递归异步 promise ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32525419/

相关文章:

javascript - 上传脚本 - jQuery 参数内的 Javascript 变量

javascript - 默认值不是?选择选项 Angularjs 中的对象 :null ?

javascript - Node.js async.each - "callback was already called"

javascript - 如何将选项卡设置为禁用/启用

javascript - 使用 jQuery 更改导航

python - python ;异步处理错误我需要一个单独的线程吗?

javascript - 学习 Nodejs,回调和 fs.readFile 的问题

javascript - 在 NodeJS 中使用 Promise 递归创建无限循环

java - 递归语句中声明的变量是创建新变量还是刷新原始变量?

java - 递归函数返回类型错误