如何解决递归异步承诺?

2024-01-18

我正在玩弄承诺,并且在异步递归承诺方面遇到了麻烦。

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

编辑以澄清 :

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

End edit

我希望能够写出以下内容

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

where

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从未得到解决。我怎样才能确保这个承诺得到履行,以便printTime叫做?

代替

resolve(athlete);

我可以

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

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

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

错误在于您正在传递一个返回承诺的函数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);
    }    
  });
};

但是嗯。我认为这是一个很好的例子,说明为什么人们应该避免承诺构造函数反模式 https://stackoverflow.com/q/23803743/1048572(因为混合承诺代码和非承诺代码不可避免地会导致这样的错误)。

我遵循的避免此类错误的最佳实践:

  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返回运动员或承诺。这在这里很好,因为.then函数会自动提升传递给 Promise 的函数的返回值。如果你会打电话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);
});

然后这样做:

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>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何解决递归异步承诺? 的相关文章

  • 使用 ScriptEngine 从 JavaScript 调用 Java 方法

    我正在使用 ScriptEngine 运行 JavaScript 我希望 JavaScript 脚本能够调用 myFunction 其中 myFunction 是我的给定类中的一个方法 我知道可以将 importPackage 用于标准 J
  • Jest 中从未调用图像 onLoad 处理程序

    我正在尝试使用 Jest 测试将 dataUrl 加载到图像中 我正在使用 JSDOM 并按照说明添加resources usable 作为一个选项 如果我直接从 Node 运行该代码 则该代码可以工作 但是当我尝试在 Jest 中运行它时
  • JS如何获取多维数组的最大深度?

    我有一个多维数组 我想知道它的最大深度 我发现了这个灵魂 但它不适用于对象数组 const getArrayDepth arr gt return Array isArray arr 1 Math max arr map getArrayD
  • Vue js按钮冻结dom

    我试图在按下按钮时切换包含加载动画的跨度 直到使用 v if 函数完成 但是当我按下按钮时 DOM 冻结并且 span 元素保持不变 直到函数调用结束 如何让 DOM 不冻结并显示加载图标 非阻塞按钮按下可能是一个解决方案 HTML
  • 将字符串转换为变量名。 (JavaScript)

    我确实查看了前面的问题 但它们是针对整数值的 我需要文本值的答案 我在本周早些时候问了一个与此相关的问题 但现在是这样 如下所示 我使 Make x 等于某个字符串值 Acura Honda Toyota 当我将 Make x 传递到函数
  • RequireJS 不遵循设置了 baseUrl 的 data-main 的相对路径

    使用 requireJS 我尝试为我的数据主指定一个与 baseUrl 不同的路径 看来 requireJS 会忽略我在文件名之前输入的任何内容 并始终在 baseUrl 文件夹中查找该文件 我有以下文件夹结构 index html scr
  • TypeScript 中类和命名空间的区别

    到底有什么区别classes and namespaces在打字稿中 我知道 如果您创建一个带有静态方法的类 您可以在不实例化该类的情况下访问它们 这正是我猜想的命名空间的要点之一 我还知道你可以创建多个同名的命名空间 并且它们的方法在编译
  • 向下滚动时如何使图像移动?

    这是我想要实现的目标的示例 https www flambette com en https www flambette com en 我尝试过更改图像的 css 属性 但效果不能满足我的需求 我尝试过以下代码 mydocument on
  • 在 Javascript 中,使用 var foo = function foo(i) { ... } 的动机或优点是什么?

    我在答案中看到 在Javascript中 为什么要写 var QueryStringToHash function QueryStringToHash query https stackoverflow com questions 3233
  • 使用 getElementById 在 javascript 中使用正则表达式进行 Html 表单验证?

    我想使用正则表达式验证 html 表单的示例模式 AAA 111 2222 aa 1234 目前 我的代码要么为所有输入返回 正确 要么为所有输入返回 不正确 并且我无法弄清楚我的问题出在哪里 var x document getEleme
  • 如何正确关闭 Node.js Express 服务器?

    我需要在收到回调后关闭服务器 auth github callback网址 与平常一样HTTP API http nodejs org docs latest api http html关闭 服务器目前支持server close call
  • 表单提交不起作用

    我有一张桌子 可以打印出所有可用的相机 它使用表单来更改这些设置 问题在于该表单仅更新条目中的最后一个摄像机 换句话说 如果我更改表单并为列表中的最后一个摄像机点击 应用 它将起作用 如果我更改此列表中任何其他摄像机的表单 它会将其更改为与
  • Google Maps JS Api - b.get 不是函数错误(isLocationOnEdge)

    我想检查我的路线上是否有标记 所以我尝试使用 isLocationOnEdge 但收到 TypeError b get 不是函数 错误 这是我的代码 我尝试了几次更改但无法解决问题 var directionsDisplay new goo
  • JQuery _renderItem 没有被调用

    我正在尝试使用 renderItem 函数创建自定义 ui menu item 元素 但经过可能尝试后 我什至无法调用该函数 自动完成功能正在工作 但就像 renderItem 函数不存在一样 这是我的脚本部分
  • 为什么在 vue 组件上输入另一个输入时,输入文件的值丢失了?

    我有两个组件 我的第一个组件 父组件 如下所示
  • 检测 JavaScript 中的焦点丢失

    我希望能够检测 JavaScript 中任意元素何时失去焦点 因此我可以构建一个类似于 jEdit 的内联编辑工具 我不能依赖 jQuery 来实现这个库 所以我需要一个本机方法来完成它 我查看了 onblur 这似乎是正确的事情 但 MD
  • 传单 - 导入 Geojson - Angular 6

    我尝试将 GeoJson 文件导入到 Angular 的应用程序 6 中的传单中 通过这个解决方案 我的 geojson 是在 leafletmap 中绘制的 但我有这个错误 我无法构建我的应用程序 有人知道一种解决方案吗 错误 TS234
  • Niceedit本地上传图片失败

    我是这样称呼编辑的 new nicEditor buttonList bold italic underline upload iconsPath img nicedit png uploadURI http server com inte
  • Javascript/DOM:如何删除 DOM 对象的所有事件侦听器?

    只是问题 有没有办法完全删除对象的所有事件 例如一个div 编辑 我添加每div addEventListener click eventReturner false 一个事件 function eventReturner return f
  • 如何在javascript中解析 yyyy-MM-dd HH:mm:ss.SSS 格式的日期?

    const time 2016 11 16 00 00 00 000 const date new Date time console info date 似乎 safari 无法解析 yyyy MM dd HH mm ss SSS 格式日

随机推荐