Wait an event to resolve a promise - javascript

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)
})

Related

How can I handle asynchronous functions called in a loop?

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();
}
});
}

Node.js Promises within promises not waiting for for loop to return data

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.

How to maintain Timeout Session when calling Api Call in Loop Contion

i having Api Call which execute in For Loop some of the value which returns 10 sec itself some may take nearly 60 sec i have to maintain proper Timeout and clear session (i.e if results comes at 15 sec means it should goes to next input values and run the code) but currenly its waiting for 45 sec each single record how to optimize it
here my sample code :
if (selectedrows.length >= 1) {
for (var i = 0; i < selectedrows.length; i++) {
var myVar = setTimeout (function (k) {
var ob = { results: "Appending ..." };
child.update(selectedrows[k][4], selectedrows[k][4], ob);
var fullName = selectedrows[k][1] + ' ' + selectedrows[k][2];
math.ResultCall.async(fullName,function (err, res) {
if (err) throw err;
var returnedValue = JSON.parse(res);
console.log(returnedValue);
if(returnedValue.Result == null || returnedValue.Result.FOUND_Result == null)
{
console.log("None found")
}
else{
var obj = { results: “res” };
child.update(selectedrows[k][4], selectedrows[k][4], obj);
}
}
});
}, i * 45000,i);
}
}
Rephrasing your question, you need to return the data when your api gets resolved.
For this please go through https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
JavaScript, by default it work asynchronously because of its event loop.
You have promises and resolve to get notified when your api returns a data
Hope I helped :)
There are several approaches to implement the solution
1. Async-Await: in-case the records-processing order is important
for( let i=0; i<selectedrows.length; i++)
{
let ob = { results: "Appending ..." };
child.update(selectedrows[i][4], selectedrows[i][4], ob);
let fullName = selectedrows[i][1] + ' ' + selectedrows[i][2];
await new Promise((resolve,reject)=>
{
math.ResultCall.async(fullName,(err, res) => {
if (err) reject(err);
let returnedValue = JSON.parse(res);
console.log(returnedValue);
if(returnedValue.Result == null || returnedValue.Result.FOUND_Result == null) {
console.log("None found")
} else {
let obj = { results: “res” };
child.update(selectedrows[i][4], selectedrows[i][4], obj);
}
resolve();
});
}
**don't forget this means the wrapping function should be async as well (which returns a promise that can be resolved if necessary)
2.Promise.All: if the order is not important
let promArray = [];
for( let i=0; i<selectedrows.length; i++)
{
let ob = { results: "Appending ..." };
child.update(selectedrows[i][4], selectedrows[i][4], ob);
let fullName = selectedrows[i][1] + ' ' + selectedrows[i][2];
promArray.push( new Promise((resolve,reject)=>
{
math.ResultCall.async(fullName,(err, res) => {
if (err) reject(err);
let returnedValue = JSON.parse(res);
console.log(returnedValue);
if(returnedValue.Result == null || returnedValue.Result.FOUND_Result == null) {
console.log("None found")
} else {
let obj = { results: “res” };
child.update(selectedrows[i][4], selectedrows[i][4], obj);
}
resolve();
});
);
}
Promise.all(promArray);
** this will also return a Promise that can be resolved if necessary.

Iterate function and stop depending on callback value

How do I iterate over a asynchronous function in nodejs stopping depending on callback return?
Example:
suppose I have following function
var fn1 = function(i, callback){
// code here
callfunction(i, function(err, p2){
if(err) throw err;
if(something) return callback(true);
else return callback(false);
});
}
I need to iterate and call for i=1,2,3,... until my callback returns false.
I tried using async whilst, but I do not think it can helps me, because the test function also needs callback, and the function for test and iteratee are same, I cannot call twice.
If I understand your problem correctly. What about some recursion
const maxTries = 10;
// just a stupid counter to run a be able to change return value
let numberOfCalls = 0;
const asyncCall = () => {
if (numberOfCalls === 9) {
return Promise.resolve(false);
}
numberOfCalls += 1;
return Promise.resolve();
};
function iterate(calls = 0) {
if (maxTries === calls) {
return Promise.resolve();
}
return asyncCall().then(value => {
if (value === false) {
return value;
}
return iterate(calls + 1);
});
}
iterate().then(result => {
if (result === undefined) {
//to many tries
}
console.log('result', result);
});

How to execute a promise first and then move to the next statement in JavaScript

What I want to do is wait for the second promise to finish, concatenate the data i.e data = data.concat(items) and then increment the count making the loop run specified times. It's all written in AngularJS.
DataService.getSomeData().then(function(data) {
let count = 1;
while (count < 3) {
if (someCondition) { // It evaluates to true
// Second Promise
DataService.getUserData().then(function(items) {
data = data.concat(items); // --------> this should run before incrementing the count
});
count++;
}
$scope.myData = data;
}
});
Thanks!
Keep the api returning promises - its the easiest to handle and most predictable...
DataService.getSomeData()
.then(someData => {
let count = 1;
const promises = [];
while (count < 3) {
if (someCondition) { // It evaluates to true
promises.push(DataService.getUserData());
count++;
}
}
return $q.all(promises)
.then(data => data.reduce((memo, data) => memo.concat(data), someData));
})
.then(data => $scope.myData = data);
#Aleksey Solovey has already mentioned the solution which is to use $q.all(), there is another method of recursion which you can make use of.
DataService.getSomeData().then(function(data) {
getUserDataRecursion(data,0).then(result=>{
$scope.myData = result;
}).catch(error=>{
console.log("error handle ",error)
})
});
getUserDataRecursion(data,count){
return new Promise((resolve,reject)=>{
if(count<3){
if (someCondition){
DataService.getUserData().then((items) {
data = data.concat(items);
count++;
getUserDataRecursion(data,count),then(()=>{
resolve(data);
})
});
}else{
resolve(data);
}
}else{
resolve(data);
}
})
}

Categories