[英]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.