[英]How to resolve recursive asynchronous promises?
我正在玩promise,但遇到异步递归promise的麻烦。
场景是一名运动员开始跑100m,我需要定期检查他们是否已经完成,并且一旦完成,就打印他们的时间。
编辑以澄清 :
在现实世界中,运动员正在服务器上运行。 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
创建的承诺从未解决过。 如何确保已解决诺言, printTime
调用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);
}
});
};
但是,嗯。 我认为这是一个很好的示例,说明了为什么应该避免使用promise-constructor反模式 (因为混合使用promise代码和非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 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>
使用setTimeout
/ setInterval
是不能很好地兑现诺言的场景之一,并且会导致您使用皱眉的诺言反模式。
话虽如此,如果您将函数重构为“等待完成”类型的函数(并相应地命名),则可以解决您的问题。 waitForFinish
函数仅被调用一次,并返回一个promise(尽管在startRunning
创建的原始promise的基础上是一个新startRunning
)。 通过setTimeout
处理重复发生是在内部轮询功能中完成的,其中使用适当的try / catch来确保将异常传播到Promise。
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 waitForFinish = function (athlete) { return new Promise(function(resolve, reject) { (function pollFinished() { try{ if (athlete.distanceTravelled >= 100) { clearInterval(intervalID); console.log("finished"); resolve(athlete); } else { if(Date.now()%1000 < 250) { // This is here to show errors are cought throw new Error('some error'); } console.log("not finished yet, check again in a bit"); setTimeout(pollFinished, 1000); } } catch(e) { // When an error is cought, the promise is properly rejected // (Athlete will keep running though) reject(e); } })(); }); }; var printTime = function (athlete) { console.log('printing time', athlete.timeTaken); }; var handleError = function (e) { console.log('Handling error:', e); }; startRunning() .then(waitForFinish) .then(printTime) .catch(handleError);
尽管所有这些代码都可以正常运行,但绝不建议在异步环境中使用轮询解决方案,并且应尽可能避免使用轮询解决方案。 在您的情况下,由于此示例模拟了与服务器的通信,因此,如果可能,我会考虑使用Web套接字。
由于您对诺言的使用还差得很远,因此很难确切说明您要做什么或最适合哪种实现,但这是一个建议。
承诺是一站式状态机。 这样,您将返回一个承诺,并且恰好在将来的某个时间返回该承诺,或者有理由拒绝该承诺,也可以通过值来解决该承诺。 鉴于承诺的设计,我认为有意义的是可以这样使用:
startRunning(100).then(printTime, handleError);
您可以使用以下代码来实现:
function startRunning(limit) {
return new Promise(function (resolve, reject) {
var timeStart = Date.now();
var athlete = {
timeTaken: 0,
distanceTravelled: 0
};
function updateAthlete() {
athlete.distanceTravelled += 25;
console.log("updated athlete", athlete)
if (athlete.distanceTravelled >= limit) {
clearInterval(intervalID);
athlete.timeTaken = Date.now() - timeStart;
resolve(athlete);
}
}
var intervalID = setInterval(updateAthlete, 2500);
});
}
function printTime(athlete) {
console.log('printing time', athlete.timeTaken);
}
function handleError(e) {
console.log(e);
}
startRunning(100).then(printTime, handleError);
工作演示: http : //jsfiddle.net/jfriend00/fbmbrc8s/
仅供参考,我的设计偏好可能是拥有一个公共运动员对象,然后对该对象执行开始运行,停止运行等方法。
以下是在使用诺言时犯错的一些基本问题:
startRunning().then(checkIsFinished)
只是不合逻辑。 为了使此工作的第一部分startRunning()
, startRunning()
必须返回一个promise,并且它必须在发生有用的事情时解析或拒绝该promise。 您只是在两秒钟后解决了问题,但这似乎并没有完成任何有用的工作。 这是创建公共Athlete()
对象的另一种方法,该对象具有某些方法,并允许多个人监视进度:
var EventEmitter = require('events');
function Athlete() {
// private instance variables
var runInterval, startTime;
var watcher = new EventEmitter();
// public instance variables
this.timeTaken = 0;
this.distanceTravelled = 0;
this.startRunning = function() {
startTime = Date.now();
var self = this;
if (runInterval) {clearInterval(runInterval);}
runInterval = setInterval(function() {
self.distanceTravelled += 25;
self.timeTaken = Date.now() - startTime;
console.log("distance = ", self.distanceTravelled);
// notify watchers
watcher.emit("distanceUpdate");
},2500);
}
this.notify = function(limit) {
var self = this;
return new Promise(function(resolve, reject) {
function update() {
if (self.distanceTravelled >= limit) {
watcher.removeListener("distanceUpdate", update);
resolve(self);
// if no more watchers, then stop the running timer
if (watcher.listeners("distanceUpdate").length === 0) {
clearInterval(runInterval);
}
}
}
watcher.on("distanceUpdate", update);
});
}
}
var a = new Athlete();
a.startRunning();
a.notify(100).then(function() {
console.log("done");
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.