如何解决递归异步承诺?

use*_*596 5 javascript recursion asynchronous promise es6-promise

我正在玩承诺,我遇到异步递归承诺的问题.

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

编辑澄清:

在现实世界中,运动员在服务器上运行.startRunning涉及对服务器进行ajax调用.checkIsFinished还涉及对服务器进行ajax调用.下面的代码试图模仿它.代码中的时间和距离是硬编码的,以尽量使事情变得简单.道歉不清楚.

结束编辑

我希望能够写下以下内容

startRunning()
  .then(checkIsFinished)
  .then(printTime)
  .catch(handleError)
Run Code Online (Sandbox Code Playgroud)

哪里

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); };
Run Code Online (Sandbox Code Playgroud)

我可以看到,第一次创建的承诺checkIsFinished永远不会得到解决.如何确保该承诺得到解决以便printTime调用?

代替

resolve(athlete);
Run Code Online (Sandbox Code Playgroud)

我可以

Promise.resolve(athlete).then(printTime);
Run Code Online (Sandbox Code Playgroud)

但我想尽可能避免这种情况,我真的很想能够写作

startRunning()
  .then(checkIsFinished)
  .then(printTime)
  .catch(handleError)
Run Code Online (Sandbox Code Playgroud)

jib*_*jib 9

错误是你传递的函数返回一个promise setTimeout.这个承诺在以太中消失了.创可贴修复可能是在执行函数上进行的:

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);
    }    
  });
};
Run Code Online (Sandbox Code Playgroud)

但是,好吧.我认为这是一个很好的例子,说明为什么人们应该避免promise-constructor反模式(因为混合承诺代码和非承诺代码不可避免地导致这样的错误).

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

  1. 只处理返回promises的异步函数.
  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);
Run Code Online (Sandbox Code Playgroud)
<div id="div"></div>
Run Code Online (Sandbox Code Playgroud)

请注意,checkIsFinished返回运动员或承诺.这很好,因为.then函数会自动提升您传递给promises的函数的返回值.如果您要checkIsFinished在其他环境中进行呼叫,您可能希望自己进行宣传,return Promise.resolve(athlete);而不是使用return athlete;.

编辑以回应阿米特的评论:

对于非递归答案,请checkIsFinished使用此帮助程序替换整个函数:

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

然后这样做:

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);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)
<div id="div"></div>
Run Code Online (Sandbox Code Playgroud)

  • 当您的“最佳实践”列表看起来很称职时,它会带您进入一个场景,在该场景中您递归地创建新的Promise,而不是链接,而是在构建越来越深的“承诺栈”。每个`wait(1000).then(...)`都承载着一个新的Promise-类似于`wait(x).then(()=&gt; wait(x).then(()=&gt; wait(x))。然后(...)))`。如果最终结果到来,这将对内存和性能产生不利影响。更糟糕的是,所有这些细节都隐藏在浮华的语法和抽象中。 (2认同)