我在玩弄 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 代码不可避免地会导致这样的错误)。
我遵循的避免此类错误的最佳做法:
- 只处理返回 promise 的异步函数。
- 当一个人不返回 promise 时,用 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
返回运动员或 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/