[英]How to resolve recursive asynchronous promises?
I'm playing around with promises and I'm having trouble with an asynchronous recursive promise. 我正在玩promise,但遇到异步递归promise的麻烦。
The scenario is an athlete starts running the 100m, I need to periodically check to see if they have finished and once they have finished, print their time. 场景是一名运动员开始跑100m,我需要定期检查他们是否已经完成,并且一旦完成,就打印他们的时间。
Edit to clarify : 编辑以澄清 :
In the real world the athlete is running on a server. 在现实世界中,运动员正在服务器上运行。
startRunning
involves making an ajax call to the server. startRunning
涉及对服务器进行ajax调用。 checkIsFinished
also involves making an ajax call to the server. checkIsFinished
还涉及对服务器进行ajax调用。 The code below is an attempt to imitate that. 下面的代码是试图模仿它。 The times and distances in the code are hardcoded in an attempt to keep things as simple as possible.
代码中的时间和距离被硬编码,以使事情尽可能简单。 Apologies for not being clearer.
抱歉,不清楚。
End edit 结束编辑
I'd like to be able to write the following 我想写以下内容
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); };
I can see that the promise that is created the first time checkIsFinished
is never resolved. 我可以看到,第一次
checkIsFinished
创建的承诺从未解决过。 How can I ensure that that promise is resolved so that printTime
is called? 如何确保已解决诺言,
printTime
调用printTime
?
Instead of 代替
resolve(athlete);
I could do 我可以做
Promise.resolve(athlete).then(printTime);
But I'd like to avoid that if possible, I'd really like to be able to write 但我想避免这种情况,我真的很想能够写
startRunning()
.then(checkIsFinished)
.then(printTime)
.catch(handleError)
The bug is that you are passing a function that returns a promise to setTimeout
. 错误是您传递了一个函数,该函数返回一个对
setTimeout
的承诺。 That promise is lost into the ether. 那个应许丢进了以太。 A band-aid fix might be to recurse on the executor function:
临时解决方案可能是递归执行程序函数:
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);
}
});
};
But meh. 但是,嗯。 I think this is a great example of why one should avoid the promise-constructor anti-pattern (because mixing promise code and non-promise code inevitably leads to bugs like this).
我认为这是一个很好的示例,说明了为什么应该避免使用promise-constructor反模式 (因为混合使用promise代码和非Promise代码不可避免地会导致此类错误)。
After this, I find code easier to reason about and harder to bugger up, because everything follows the same pattern. 在此之后,我发现代码更容易推理,更难以调试,因为所有内容都遵循相同的模式。
Applying this to your example got me here (I'm using es6 arrow functions for brevity. They work in Firefox and Chrome 45): 将其应用于您的示例使我到了这里(为了简便起见,我使用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>
Note that checkIsFinished
returns either athlete or a promise. 请注意,
checkIsFinished
返回运动员或诺言。 This is fine here because .then
functions automatically promote return values from functions you pass in to promises. 这很好,因为
.then
函数会自动提升您传递给promise的函数的返回值。 If you'll be calling checkIsFinished
in other contexts, you might want to do the promotion yourself, using return Promise.resolve(athlete);
如果要在其他情况下调用
checkIsFinished
,则可能需要使用return Promise.resolve(athlete);
自己进行return Promise.resolve(athlete);
instead of return athlete;
代替
return athlete;
. 。
Edit in response to comments from Amit : 根据Amit的评论进行编辑 :
For a non-recursive answer, replace the entire checkIsFinished
function with this helper: 对于非递归答案,请使用以下帮助器替换整个
checkIsFinished
函数:
var waitUntil = (func, ms) => new Promise((resolve, reject) => {
var interval = setInterval(() => {
try { func() && resolve(clearInterval(interval)); } catch (e) { reject(e); }
}, ms);
});
and then do this: 然后执行以下操作:
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>
Using setTimeout
/ setInterval
is one of the scenrios that doesn't play well with promises, and causes you to use the frowned promise anti-pattern. 使用
setTimeout
/ setInterval
是不能很好地兑现诺言的场景之一,并且会导致您使用皱眉的诺言反模式。
Having said that, if you reconstruct your function make it a "wait for completion" type of function (and name it accordingly as well), you'd be able to solve your problem. 话虽如此,如果您将函数重构为“等待完成”类型的函数(并相应地命名),则可以解决您的问题。 The
waitForFinish
function is only called once, and returns a single promise (albeit a new one, on top of the original promise created in startRunning
). waitForFinish
函数仅被调用一次,并返回一个promise(尽管在startRunning
创建的原始promise的基础上是一个新startRunning
)。 The handling of the recurrence through setTimeout
is done in an internal polling function, where proper try/catch is used to ensure exceptions are propagated to the promise. 通过
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);
While all this code is functioning properly, a polling solution is never advised in an asynchronous environment and should be avoided if possible. 尽管所有这些代码都可以正常运行,但绝不建议在异步环境中使用轮询解决方案,并且应尽可能避免使用轮询解决方案。 In your case, since this sample mocks communication with a server, I'd consider using web sockets if possible.
在您的情况下,由于此示例模拟了与服务器的通信,因此,如果可能,我会考虑使用Web套接字。
Since your use of promises is pretty off the mark, it's a little hard to tell exactly what you're trying to do or what implementation would best fit, but here's a recommendation. 由于您对诺言的使用还差得很远,因此很难确切说明您要做什么或最适合哪种实现,但这是一个建议。
Promises are a one-shot state machine. 承诺是一站式状态机。 As such, you return a promise and exactly one time in the future, the promise can be either rejected with a reason or resolved with a value.
这样,您将返回一个承诺,并且恰好在将来的某个时间返回该承诺,或者有理由拒绝该承诺,也可以通过值来解决该承诺。 Given that design of promises, I think what makes sense would be something that can be used like this:
鉴于承诺的设计,我认为有意义的是可以这样使用:
startRunning(100).then(printTime, handleError);
You could implement that with code like this: 您可以使用以下代码来实现:
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);
Working demo: http://jsfiddle.net/jfriend00/fbmbrc8s/ 工作演示: http : //jsfiddle.net/jfriend00/fbmbrc8s/
FYI, my design preference would probably be to have a public athlete object and then methods on that object to start running, stop running, etc... 仅供参考,我的设计偏好可能是拥有一个公共运动员对象,然后对该对象执行开始运行,停止运行等方法。
Here are some of the fundamental things you got wrong in a use of promises: 以下是在使用诺言时犯错的一些基本问题:
startRunning().then(checkIsFinished)
just doesn't make logical sense. startRunning().then(checkIsFinished)
只是不合逻辑。 For the first part of this to work, startRunning()
has to return a promise, and it has to resolve ore reject that promise when something useful happens. startRunning()
, startRunning()
必须返回一个promise,并且它必须在发生有用的事情时解析或拒绝该promise。 Your are just resolving it after two seconds which doesn't really seem to accomplish anything useful. Here's another approach that creates a public Athlete()
object that has some methods and allows multiple people to be watching the progress: 这是创建公共
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.