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();
}
});
}
Related
I have a Subject, which used to be fire async chain.
Its sometimes looks like working correctly, sometimes looks like the subscribe doesn't wait for the resolve.
behavior.subscribe(async (result)=> {
await fnc1(result);
});
fnc1(in) {
return new Promise(async (resolve,reject) => {
for(let w of in.split(',')) {
await fnc2(w);
}
//if for finished, go to next subscribed value
resolve();
});
}
fnc2(w) {
return new Promise((resolve,reject) => {
let i = 0;
setInterval( () => {
i = i + 1;
if(i == 10) {
//go next value in for loop
resolve();
}
},100);
});
}
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
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'm having the function which is wait for sync after that it will load the content. Below function is working perfectly in firefox but not working in IE11
//Working in other browser and inserting the multiple records but not in IE
async function setup()
{
await Word.run(async(context)=> {
for (var i=0; i < 5; i++)
{
var controler = context.document.contentControls.getByTag("myTag"+i);
controler.load();
await context.sync();
controler.items[0].insertPargraph("Adding paragraph "+i);
}
}
)};
}
For IE11, below function is working perfectly for inserting only one record
//Working in IE for the only one record
function setUp()
{
Word.run(function (context){
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
})
}
Now problem is I want to iterate the loop for the contents, I have written the return function inside the forloop that the reason it is not working
//Below function is not working
function setUp()
{
Word.run(function (context){
for (var i=0; i < 5; i++)
{
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
}
})
}
How to write the await function for IE11 browsers. I have tried the goto Lable function but that also not working.
Your async version uses i with getTag and when adding the paragraph, but your subsequent code example doesn't. It matters for the solution.
Common Ground
You can create a promise chain, similar to my answer here but different enough it might be hard to apply that to your case. Basically, you start with a resolved promise (p), then use p = p.then(...) to build the chain.
If you don't need to use i's value
...then you can do it like this:
function setUp()
{
Word.run(function (context){
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(function() {
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
});
}
})
}
If you do need to use i's value
...then we need to bake it into the code since you have to use var (IE11 has let, but it doesn't have ES2015 semantics for for loops):
function setUp()
{
Word.run(function (context){
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
})
}
Giving setUp a return value
Your async version assumes that Word.run returns a promise and that it expects its callback to return a promise. I can't find any documentation to support that, but then, the web documentation for this stuff appears to be truly amazingly bad.
If both of those assumptions are true, then to haev setUp return a promise, we'd only need to make small changes: return before Word.run and return p; at the end of the callback (see *** comments);
function setUp()
{
return Word.run(function (context){ // ***
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
return p; // ***
})
}
But if Word.run doesn't return a promise, or doesn't expect a promise from its callback, that won't work and we have to create our own:
function setUp()
{
return new Promise(function(resolve, reject) { // ***
Word.run(function (context) {
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
p.then(resolve).catch(reject); // ***
})
});
}
I think what you are trying to achieve is to chain the sync() calls together so that the callback in Word.run only resolves when everything is syncd. You can do this using Promise.all() to generate a promise resolving when all provided promises have resolved.
function setUp() {
Word.run(function(context) {
const promises = [];
for (var i = 0; i < 5; i++) {
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag, 'text');
let p = context.sync().then(function() {
controler.items[0].insertPargraph("Adding paragraph 0")
});
promises.push(p);
}
return Promise.all(promises);
})
}
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)
})