I want to execute some code after a certain delay in a loop. And once all the iterations are done, I want to do some other tasks. These depend on the results obtained from task1. For this I have written the following code snippet using generator, async/await and promise:
function* iter() {
for (var i = 0; i < 10; i++) yield i
}
async function start() {
var myIter = iter();
var p = await cb1(myIter);
console.log('after await');
p.then((value) => {
console.log('-----------here-------------');
});
}
start();
function cb1(myIter) {
console.log("Started : " + new Date());
var obj;
return new Promise((resolve, reject) => {
setTimeout(function(){
if(myIter.next().done === true) {
console.log("End : " + new Date());
resolve('done');
}else {
console.log("---in else---");
cb1(myIter);
}
}, 3000);
});
}
The issue is, the console in p.then() never gets printed. This means that the promise never gets resolved and the program terminates. The iterations execute as expected, but the promise never resolves. What could be wrong here? I am using recursion to trigger iterator.next() and want to resolve the promise only on the last iteration, that is when done=true.
Scratching my head since long on this issue. Help is appreciated. Following is the output of this program.
A couple problems: the line
var p = await cb1(myIter);
results in p being assigned the value of the result of calling cb1. p is not a Promise unless the constructed Promise resolves to a Promise as well, which is unusual. Because await essentially pauses the execution of the script until the promise resolves, you don't need .then - you just need to add the console.log below in the start function. Once you chain the promises together properly, p will resolve to a string of 'done', which of course isn't a Promise.
But there's another problem: your promises returned by cb1 never resolve, except at the very end, where yuo're calling resolve. In the else, you're not ever calling resolve, so those promises remains unresolved forever. To fix this, change
} else {
console.log("---in else---");
cb1(myIter);
}
to
} else {
console.log("---in else---");
cb1(myIter).then(resolve);
}
so that the current iteration's Promise resolves once the next iteration's Promise resolves.
function* iter() {
for (var i = 0; i < 3; i++) yield i
}
async function start() {
var myIter = iter();
var p = await cb1(myIter);
console.log('after await');
console.log('-----------here-------------');
}
start();
function cb1(myIter) {
console.log("Started : " + new Date());
var obj;
return new Promise((resolve, reject) => {
setTimeout(function() {
if (myIter.next().done === true) {
console.log("End : " + new Date());
resolve('done');
} else {
console.log("---in else---");
cb1(myIter).then(resolve);
}
}, 1000);
});
}
There are a couple of issues:
The result of await will never be a promise; the purpose of await is to wait for the promise to resolve and give you the resolution value. So p in your code isn't a promise, and won't have a then method. But you're not getting an error about that because of #2.
Each call to cb1 creates a new promise, with a new resolve function. Your last setTimeout callback is resolving the last promise, but nothing ever resolves the first one, so you never get past that var p = await cb1(myIter); line.
You probably want an inner function for the timer callback, and then have the promise returned by the cb1 call resolve.
Something along these lines:
function* iter() {
for (var i = 0; i < 10; i++) {
yield i;
}
}
async function start() {
var myIter = iter();
var p = await cb1(myIter);
console.log("p = ", p);
}
start();
function cb1(myIter) {
console.log("Started : " + new Date());
return new Promise((resolve, reject) => {
iteration();
function iteration() {
setTimeout(function() {
if (myIter.next().done) { // `=== done` is pointless here
console.log("End : " + new Date());
resolve('done');
} else {
console.log("---in else---");
iteration();
}
}, 3000);
}
});
}
Need 1 more function then it works:
function* iter() {
for (var i = 0; i < 10; i++) yield i
}
async function start() {
var myIter = iter();
var p = await cb1(myIter);
console.log('after await');
console.log("here is p:" + p.done + "," + p.value);
}
start();
function repeat(myIter,resolver,previous){
var temp;
if((temp = myIter.next()).done === true) {
console.log("End : " + new Date());
resolver(previous);
}else {
console.log("---in else---");
setTimeout(function(){repeat(myIter,resolver,temp)},3000);
}
}
function cb1(myIter) {
console.log("Started : " + new Date());
var obj;
return new Promise((resolve, reject) => {
repeat(myIter,resolve);
});
}
Also fixed the p, await gets it out, and you needed to grab the previous value
Related
my function greetings works but when i try to return a promise to execute things once its done im getting nothing, what am i missing?
I have tried putting return resolve() as to make sure the function ends but still nothing, I can´t get the .then() to execute.
const greetingDivs = [firstDiv, secondDiv, thirdDiv, fourthDiv, fifthDiv]
let y = 0
function greetings() {
return new Promise((resolve, reject) => {
if (y == greetingDivs.length) {
// console.log('resolved')
resolve('done')
}
if (y != greetingDivs.length) {
setTimeout(() => {
let lastDiv = consoleOutput.appendChild(greetingDivs[y])
.scrollIntoView(false, { behavior: 'smooth' })
y++
greetings()
}, 300)
}
})
}
greetings().then(() => {
console.log('hello')
})
Your code only resolves one promise, while it creates 5. Notably, the first one, the one that greetings() returns, never resolves.
I would suggest promisifying setTimeout, so you have that logic once and for all. Then the looping can be done in an async function with a for loop and await:
const consoleOutput = document.body;
const [firstDiv, secondDiv, thirdDiv, fourthDiv, fifthDiv] = [
"Hello", "Welcome to this demo", "Hope it suits your case", "Test it", "and enjoy"].map(s => {
const div = document.createElement("div");
div.textContent = s;
return div;
});
const greetingDivs = [firstDiv, secondDiv, thirdDiv, fourthDiv, fifthDiv];
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function greetings() {
for (let div of greetingDivs) {
consoleOutput.appendChild(div)
.scrollIntoView(false, { behavior: 'smooth' });
await delay(300);
}
}
greetings().then(() => {
console.log('hello');
});
See ES6 promise for loop for more ideas on how you can create such loops.
I'm trying to understand how to get a for loop to wait for a function to complete before moving on to the next iteration. I think I want to use a callback, but I'm unsure how to go about it. In my example, I'd like for the loop to wait for the setTimeout so that the numbers in the array are displayed in the proper order:
var myArr = [1,2,3];
$('#myButton').on('click',function1);
function function1() {
for(var i = 0; i < myArr.length; i++) {
function2(myArr[i]);
}
}
function function2(number) {
if(number == 2) {
setTimeout(function(){$('#myDiv').append('<div>'+number+'</div>');},1000);
} else {
$('#myDiv').append('<div>'+number+'</div>');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="myButton">test</button>
<div id="myDiv"></div>
You can use Promise and async/await to achieve this. Return a promise from function2() and if the condition is met resolve the Promise after some time else resolve it immediately. Here's a working example:
async function function1() {
for (var i = 0; i < myArr.length; i++) {
await function2(myArr[i]);
}
}
function function2(number) {
return new Promise((resolve, reject) => {
if (number == 2) {
setTimeout(function () {
$('#myDiv').append('<div>' + number + '</div>');
resolve();
}, 1000);
} else {
$('#myDiv').append('<div>' + number + '</div>');
resolve();
}
});
}
function page() {
return new Promise((resolve, reject) => {
setTimeout(function() {
fo = $("#root>div>div>main>div>div:nth-child(3)>div>div:nth-child(2)>div>div>div:nth-child(2)>div:nth-child(2)>div>div:nth-child(2)>div>div:nth-child(2)>div>ul>li:nth-last-child(1)")
fo.click()
console.log('翻页')
resolve();
}, 200)
})
}
function l() {
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(() => {
return new Promise(resolve =>
setTimeout(function() {
console.log('wo')
$('#root>div>div>main>div>div:nth-child(3)>div>div:nth-child(2)>div>div>div:nth-child(2)>div:nth-child(2)>div>div:nth-child(2)>div>div:nth-child(1)>div:nth-child(1)>table>tbody>tr>td:nth-child(7)>div>div:nth-child(2)>a>span').eq(i).click()
resolve();
}, 200)
)
})
.then(() => {
return new Promise(resolve =>
setTimeout(function() {
console.log('wocao')
$('body>div:nth-last-child(1)>div>div>div:nth-child(3)>div:nth-child(2)>button>span').click()
resolve();
}, 200)
)
}
)
}
}
Promise.resolve().then(l).then(page)
Why is the program working not in order of the promises? How can I solve this problem? I've tried many times but they still execute together but not in order. Could somebody teach me? Very thanks.
Your function l is a unit function (i.e. returns nothing) and is therefore not awaitable as it does not return a Promise (either explicitly, or via being marked as async).
You should:
function l() {
let p = Promise.resolve() // this needs to exist out of the loop scope
for (let i = 0; i < 10; i++) {
p = p.then( /* ... */ )
}
return p; // <-- so it can be returned here
}
You need to resolve inside the timeout
new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done"
setTimeout(() => {console.log("done");resolve()}, 1000);
}).then(function (res2, rej2) {
setTimeout(() => {console.log("done2")}, 1000);
})
but in your case you do not define parameters to the promise, hence they are default.
The return Promise.all([photoArray]) returns an empty array, seemingly not waiting for the callFB to return its promise that then pushes into the array.
I am not sure what I am doing wrong but am relatively new to Promises with for loops and Ifs.
I am not sure exactly if I am using the correct number of Promises but I seem to not be able to get the 3rd tier Promise.all to wait for the for loop to actually finish (in this scenario, the for loop has to look through many item so this is causing an issue where it is not triggering callFeedback for all the items it should before context.done() gets called.
I have tried using Q.all also for the Promise.all([photoArray]) but have been unable to get that working.
module.exports = function (context, myBlob) {
var res = myBlob
var promiseResolved = checkPhoto(res,context);
var promiseResolved2 = checkVideo(res,context);
Promise.all([promiseResolved, promiseResolved2]).then(function(results){
context.log(results[0], results[1]);
// context.done();
});
});
};
};
function checkPhoto(res, context){
return new Promise((resolve, reject) => {
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
Promise.all([callFB]).then(function(results){
photoArray.push(results[0]);
});
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all([photoArray]).then(function(results){
context.log("end results: " + results);
resolve(photoArray);
});
} else {
resolve('No photos');
}
})
}
function checkVideo(res, context){
return new Promise((resolve, reject) => {
same as checkPhoto
})
}
function callFeedback(context, feedbackId) {
return new Promise((resolve, reject) => {
var requestUrl = url.parse( URL );
var requestBody = {
"id": feedbackId
};
// send message to httptrigger to message bot
var body = JSON.stringify( requestBody );
const requestOptions = {
standard
};
var request = https.request(requestOptions, function(res) {
var data ="";
res.on('data', function (chunk) {
data += chunk
// context.log('Data: ' + data)
});
res.on('end', function () {
resolve("callFeedback: " + true);
})
}).on('error', function(error) {
});
request.write(body);
request.end();
})
}
The code suffers from promise construction antipattern. If there's already a promise (Promise.all(...)), there is never a need to create a new one.
Wrong behaviour is caused by that Promise.all(...).then(...) promise isn't chained. Errors aren't handled and photoArray.push(results[0]) causes race conditions because it is evaluated later than Promise.all([photoArray])....
In case things should be processed in parallel:
function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
// likely no need to wait for callFB result
// and no need for Promise.all
photoArray.push(callFB);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all(photoArray); // not [photoArray]
} else {
return 'No photos';
};
}
callFB promises don't depend on each other and thus can safely be resolved concurrently. This allows to process requests faster.
Promise.all serves a good purpose only if it's used to resolve promises in parallel, while the original code tried to resolve the results (results[0]).
In case things should be processed in series the function benefits from async..await:
async function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
const callFBResult = await callFeedback(context, feedbackId);
// no need for Promise.all
photoArray.push(callFBResult);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return photoArray; // no need for Promise.all, the array contains results
} else {
return 'No photos';
};
}
Add try..catch to taste.
I am using a node.js module that has a method without callbacks. Instead of it, has an event that fires when that method has finished. I want resolve a promise, using that event as callback securing me that method has been completed succesfully.
array.lenght on promise can be X. So, I need 'hear' X times the event to secure me that all methods has completed succesfully <-- This is not the problem, I am just telling you that I know this could happen
Event :
tf2.on('craftingComplete', function(recipe, itemsGained){
if(recipe == -1){
console.log('CRAFT FAILED')
}
else{
countOfCraft++;
console.log('Craft completed! Got a new Item #'+itemsGained);
}
})
Promise:
const craftWepsByClass = function(array, heroClass){
return new Promise(function (resolve, reject){
if(array.length < 2){
console.log('Done crafting weps of '+heroClass);
return resolve();
}
else{
for (var i = 0; i < array.length; i+=2) {
tf2.craft([array[i].id, array[i+1].id]); // <--- this is the module method witouth callback
}
return resolve(); // <---- I want resolve this, when all tf2.craft() has been completed. I need 'hear' event many times as array.length
}
})
}
At first lets promisify the crafting:
function craft(elem){
//do whatever
return Promise((resolve,reject) =>
tf2.on('craftingComplete', (recipe,itemsGained) =>
if( recipe !== -1 ){
resolve(recipe, itemsGained);
}else{
reject("unsuccessful");
}
})
);
}
So to craft multiples then, we map our array to promises and use Promise.all:
Promise.all( array.map( craft ) )
.then(_=>"all done!")
If the events will be fired in the same order as the respective craft() calls that caused them, you can use a queue:
var queue = []; // for the tf2 instance
function getNextTf2Event() {
return new Promise(resolve => {
queue.push(resolve);
});
}
tf2.on('craftingComplete', function(recipe, itemsGained) {
var resolve = queue.shift();
if (recipe == -1) {
resolve(Promise.reject(new Error('CRAFT FAILED')));
} else {
resolve(itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
var promises = [];
for (var i = 1; i < array.length; i += 2) {
promises.push(getNextTf2Event().then(itemsGained => {
console.log('Craft completed! Got a new Item #'+itemsGained);
// return itemsGained;
}));
tf2.craft([array[i-1].id, array[i].id]);
}
return Promise.all(promises).then(allItemsGained => {
console.log('Done crafting weps of '+heroClass);
return …;
});
}
If you don't know anything about the order of the events, and there can be multiple concurrent calls to craftWepsByClass, you cannot avoid a global counter (i.e. one linked to the tf2 instance). The downside is that e.g. in two overlapping calls a = craftWepsByClass(…), b = craftWepsByClass() the a promise won't be resolved until all crafting of the second call is completed.
var waiting = []; // for the tf2 instance
var runningCraftings = 0;
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (--runningCraftings == 0) {
for (var resolve of waiting) {
resolve();
}
waiting.length = 0;
}
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
console.log('Craft completed! Got a new Item #'+itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
for (var i = 1; i < array.length; i += 2) {
runningCraftings++;
tf2.craft([array[i-1].id, array[i].id]);
}
return (runningCraftings == 0
? Promise.resolve()
: new Promise(resolve => {
waiting.push(resolve);
})
).then(() => {
console.log('Done crafting weps of '+heroClass);
});
}
Of course in both solutions you must be 100% certain that each call to craft() causes exactly one event.
You can look at the event-as-promise package. It convert events into Promise continuously until you are done with all the event processing.
When combined with async/await, you can write for-loop or while-loop easily with events. For example, we want to process data event until it return null.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
let done;
while (!done) {
const result = await eventAsPromise.upcoming();
// Some code to process the event result
process(result);
// Mark done when no more results
done = !result;
}
emitter.removeListener('data', eventAsPromise.eventListener);
If you are proficient with generator function, it may looks a bit simpler with this.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
for (let promise of eventAsPromise) {
const result = await promise;
// Some code to process the event result
process(result);
// Stop when no more results
if (!result) {
break;
}
}
emitter.removeListener('data', eventAsPromise.eventListener);
I need check if event 'craftingComplete' has fired many times as I
call tf2.craft. Doesnt matters any posible ID or if craft has failed.
I need to know if tf2.craft has finished and only why is checking
'craftingComplete' event
Given that we know i within for loop will be incremented i += 2 where i is less than .length of array, we can create a variable equal to that number before the for loop and compare i to the number within event handler
const craftWepsByClass = function(array, heroClass) {
return new Promise(function(resolve, reject) {
var countCraft = 0;
var j = 0;
var n = 0;
for (; n < array.length; n += 2);
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
countOfCraft++;
console.log('Craft completed! Got a new Item #' + itemsGained);
if (j === n) {
resolve(["complete", craftCount])
}
}
})
if (array.length < 2) {
console.log('Done crafting weps of ' + heroClass);
return resolve();
} else {
try {
for (var i = 0; i < array.length; i += 2, j += 2) {
tf2.craft([array[i].id, array[i + 1].id]);
}
} catch (err) {
console.error("catch", err);
throw err
}
}
})
}
craftWepsByClass(array, heroClass)
.then(function(data) {
console.log(data[0], data[1])
})
.catch(function(err) {
console.error(".catch", err)
})