Put on hold async loop then continue processing - javascript

I want to find out if there is better way to do this code. I'm trying to process two files and save on the database. I would like to process a an Order (parent) and when it's completed then processed the child records. If the DB is locking some records I wait 2 secs then try again.
I got it working this way, but I would like to know how can I make it better.
P.S. I cannot use async - await
var orderItems = [1, 2, 3];
var tries = 0;
saveOrder()
.then(function() {
console.log('Done Order');
try {
asyncLoop(orderItems.length, nextItem, success);
}
catch(e) {
console.log(e);
}
})
.catch ( console.log );
function nextItem(loop) {
saveItems(loop.iteration())
.then((msg) => {
console.log(loop.iteration());
loop.next();
})
.catch((e) => {
if (e instanceof String) console.log('Catched !!');
console.log(e);
tries = 1;
waitNTryAgain().then(() => { console.log('Trying again!'); nextItem(loop); });
});
}
function waitNTryAgain() {
return new Promise((resolve, reject) => {
setTimeout( resolve, 2000, 'Done waiting');
});
}
function success() {
console.log('Done asyncLoop!');
}
function saveOrder() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'Save Order Done!');
});
}
function saveItems(itemNumber) {
return new Promise((resolve, reject) => {
if (itemNumber == 1 && tries == 0) {
setTimeout(reject, 4000, 'DbLockedException');
}
else {
setTimeout(resolve, 4000, 'Save Item#: ' + itemNumber + ' Done!');
}
})
}
function asyncLoop(iterations, func, callback) {
var index = 0;
var done = false;
var loop = {
next: function() {
if (done) return;
if (index < iterations) {
index++;
func(loop);
}
else {
done = true;
callback();
}
},
iteration: function() {
return index - 1;
},
break: function() {
done = true;
callback();
}
};
loop.next();
return loop;
}

The best I could do to improve would be to create a module pattern:
var AsyncProc = (function(){
return{
orderItems : [1, 2, 3],
tries : 0,
init : function()
{
this.saveOrder()
.then(() => {
console.log('Done Order')
try
{
this.asyncLoop(this.orderItems.length, this.nextItem.bind(this), this.success)
}
catch(e) { console.log(e) }
})
.catch( console.log );
},
saveOrder : function()
{
return new Promise((resolve, reject) =>{
setTimeout(resolve, 2000, 'Save Order Done!')
})
},
nextItem : function(loop)
{
this.saveItems( loop.iteration() )
.then((msg) => {
console.log(loop.iteration())
loop.next();
})
.catch((e) =>{
console.log(e);
this.tries = 1;
this.waitNTryAgain().
then(() => { console.log('Trying again!'); this.nextItem(loop); })
})
},
waitNTryAgain : function()
{
return new Promise((resolve, reject) => {
setTimeout( resolve, 2000, 'Done waiting')
})
},
saveItems : function(itemNumber)
{
return new Promise((resolve, reject) =>{
if (itemNumber == 1 && this.tries == 0)
{
setTimeout(reject, 4000, 'DbLockedException');
}
else
{
setTimeout(resolve, 4000, 'Save Item#: '+itemNumber+' Done!');
}
});
},
success : function()
{
console.log('Done asyncLoop!')
},
asyncLoop : function(iterations, func, callback)
{
var index = 0;
var done = false;
var loop =
{
next: function()
{
if (done) return;
if (index < iterations)
{
index++;
func(loop);
}
else
{
done = true;
callback();
}
},
iteration: function()
{
return index - 1;
},
break: function()
{
done = true;
callback();
}
};
loop.next();
return loop;
}
};
})();
AsyncProc.init();

The problem with you code – you are implementing asyncLoop by yourself. It's better to use some library for it.
You can do it with https://github.com/caolan/async or http://bluebirdjs.com/docs/api
I prefer bluebird. It'll also give you some better way to handle errors.

Related

Javascript make function wait for another function before continuing

Hi I have this function that I have written.
GetIndex method
getIndex(req: connreq){
var found = false;
var number =0;
this.firereq.child(req.recipient).once('value', function(snapshot) {
if (!snapshot.exists()) {
console.log('Not exists');
this.x = '0';
}
});
}
And I call it, in this function.
SendCommand method
async sendcommand(req: connreq, cmd: string) {
this.getIndex(req);
var promise = new Promise((resolve, reject) => {
this.firereq
.child(req.recipient)
.child(this.x) .set({
sender: req.sender,
Command: cmd,
})
.then(() => {
resolve({ success: true });
})
.catch(err => {
resolve(err);
});
});
return promise;
}
However it seems that the second function continues before the getIndex method has completed. I assume we can use awaits and promises to force the sendcommand function to wait for the Getindex to finish before proceeding to the rest of it's tasks. Any assistance would be appreciated
Return a promise in the getIndex() method and execute the second in a then call back. So generally, the getIndex should be:
getIndex(req: connreq){
return new Promise((resolve,reject) =>
{
var found = false;
var number =0;
this.firereq.child(req.recipient).once('value',
function(snapshot) {
if (!snapshot.exists()) {
console.log('Not exists');
this.x = '0';
reject('Not exists');
}
resolve('exists');
});
});
}
The above will trigger the catch block if not found and the then block if found so generally it should be:
this.getIndex(req)
.then(() => {
this.firereq
.child(req.recipient)
.child(this.x) .set({
sender: req.sender,
Command: cmd,
})
.then(() => {
resolve({ success: true });
})
.catch(err => {
resolve(err);
})
})
.catch(err => {
resolve(err);
});
In the first function do the following:
getIndex(req: connreq){
return new Promise((resolve, reject) =>
let found = false;
let number =0;
this.firereq.child(req.recipient).once('value', function(snapshot) {
if (!snapshot.exists()) {
reject("not exists");
console.log('Not exists');
this.x = '0';
}
else{
resolve(snapshot.val());
}
});
});
}
Then you can do:
async sendcommand(req: connreq, cmd: string) {
this.getIndex(req).then((data)=>
{
var promise = new Promise((resolve, reject) => {
this.firereq
.child(req.recipient)
.child(this.x) .set({
sender: req.sender,
Command: cmd,
})
.then(() => {
resolve({ success: true });
})
.catch(err => {
resolve(err);
});
});
});
return promise;
}
This way getIndex() will return a promise and everything insde then() will be executed after getIndex() returns.
Return Promise from getIndex and await it in sendCommand function
getIndex(req: connreq){
return new Promise((resolve, reject) => {
var found = false;
var number =0;
this.firereq.child(req.recipient).once('value', function(snapshot) {
if (!snapshot.exists()) {
console.log('Not exists');
this.x = '0';
}
});
resolve();
}
});
and
async sendcommand(req: connreq, cmd: string) {
await this.getIndex();
....
}

Unable to syncronize the functions

I'm trying to execute the functions one by one in synchronization.
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation']
function clickMe() {
return new Promise(resolve => {
jQuery("#addtoptasksubmit").trigger("click"); // triggering the button click
resolve("done click");
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word); // input
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done")
})
};
function startLoop(i) {
new Promise(resolve => {
var promise = createSubTask();
promise.then(resolve => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.log(resolve);
});
});
});
})
}
let i = 0;
let prom = startLoop(i);
prom.then((res) => {
startLoop(i++);
})
code is not working properly and also I wanted to increment i automatically. For loop is a no show.
I've tried for loop and recursive async function on chrome.
Doesn't your startLoop(i) function need a return statement for the new Promise() call? I have to imagine this:
// prom = undefined as startLoop never returns anything
let prom = startLoop(i);
Change your code like so:
// Current code from description
function startLoop(i) {
new Promise(resolve => {
// Fixed code
function startLoop(i) {
return new Promise(resolve => {
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation'];
function clickMe() {
return new Promise(resolve => {
setTimeout(() => {
jQuery("#addtoptasksubmit").trigger("click");
resolve("done click");
}, 1000);
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word);
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done");
})
};
function startLoop(i) {
return new Promise(res => {
var promise = createSubTask();
promise.then(
(resolve) => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
console.trace(resolve);
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.trace(resolve);
res("done loop " + subtasks[i]);
});
});
}
);
})
}
var _index_ = 0;
var _how_ = setInterval(() => {
if (_index_ < subtasks.length) {
let loopPromise = startLoop(_index_);
loopPromise.then((resolve) => {
_index_ += 1;
});
} else {
console.log("all done go get the new task");
clearInterval(_how_);
}
}, 10000);
I can optimise it further....

issue with async await

I am trying to make two block of code to run sequentially with async and await
I have the code in stackblitz, open the console on Chrome to see the trace
let isMomHappy = true;
function willIgotNewPhone () {
return new Promise( (resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'samsung',
color : 'black'
};
resolve(phone);
} else {
const reason = new Error('mom not happy');
reject(reason);
}
});
}
async function showOff(phone) {
return new Promise( (resolve, reject) => {
const message = ' Hey Friend I show my phone ' + phone.brand;
resolve(message);
});
}
async function askMom() {
return await new Promise( async (resolve) => {
console.log('before asking Mom'); // log before
await willIgotNewPhone()
.then( function(fullfilled) {
console.log('Got phone from mom ' + JSON.stringify(fullfilled));
})
.catch( function(error) {
console.log(error.message);
});
console.log('after asking Mom'); // afeter log
resolve('END');
});
}
let data: any[] = [];
async function createData() {
return new Promise( (resolve, reject) => {
for (let index = 0; index < 500000; index++) {
const element: any = {};
element.id = index;
element.name = '' + index;
data.push(element);
}
if (data && data.length > 0) {
console.log(' ====== creating data size=%s', data.length);
resolve(data);
} else {
reject( new Error(' ==== Creating data Error : empty'));
}
});
}
async function callCreateData() {
return new Promise( async (resolve) => {
console.log(' ======before creating data');
await createData().then( (dataReturn: any[]) => {
console.log(' ----datareturn length=%s', dataReturn.length);
});
console.log(' ======after creating data');
});
}
async function callingMom() {
await askMom().then( (str) => {
console.log(str);
});
}
callingMom();
callCreateData();
I am calling 2 functions this.callingMom() and this.callCreateData();
and expecting the traces of the 2 functions to be sequential
I was expecting the following output
before asking Mom
Got phone from mom {"brand":"samsung","color":"black"}
after asking Mom
=====before creating creating data
===== creating data size=500000
----datareturn length=500000
===== after creating creating data
but I got the output :
before asking Mom
======before creating data
====== creating data size=500000
Got phone from mom {"brand":"samsung","color":"black"}
----datareturn length=500000
======after creating data
after asking Mom
END
Any idea what is my problem ?
Thanks
async functions can be used for two things, primarilly: to return a Promise, and to be able to use the await keyword inside. If you're not using await, or if the only await you're using is the Promise that's going to be returned, there's no point in having an async function at all - just use a normal function that returns a Promise, like in your willIgotNewPhone.
Also, in order to chain promises together, you need to use then. Simply calling asynchronous functions right after the other won't cause the thread to pause until they're done. Like this:
callingMom().then(callCreateData);
let isMomHappy = true;
function willIgotNewPhone() {
return new Promise((resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'samsung',
color: 'black'
};
resolve(phone);
} else {
const reason = new Error('mom not happy');
reject(reason);
}
});
}
function showOff(phone) {
return new Promise((resolve, reject) => {
const message = ' Hey Friend I show my phone ' + phone.brand;
resolve(message);
});
}
function askMom() {
return new Promise(async (resolve) => {
console.log('before asking Mom'); // log before
await willIgotNewPhone()
.then(function(fullfilled) {
console.log('Got phone from mom ' + JSON.stringify(fullfilled));
})
.catch(function(error) {
console.log(error.message);
});
console.log('after asking Mom'); // afeter log
resolve('END');
});
}
let data = [];
function createData() {
return new Promise((resolve, reject) => {
for (let index = 0; index < 500000; index++) {
const element = {};
element.id = index;
element.name = '' + index;
data.push(element);
}
if (data && data.length > 0) {
console.log(' ====== creating data size=%s', data.length);
resolve(data);
} else {
reject(new Error(' ==== Creating data Error : empty'));
}
});
}
function callCreateData() {
return new Promise(async (resolve) => {
console.log(' ======before creating data');
await createData().then((dataReturn) => {
console.log(' ----datareturn length=%s', dataReturn.length);
});
console.log(' ======after creating data');
});
}
function callingMom() {
return askMom().then((str) => {
console.log(str);
});
}
callingMom().then(callCreateData);

async issues with js generator and promises not returning result

I'm having yet another async issue where I'm lost and have no idea where or how to fix it. Forgive my bad naming.
api call to twitch api and returns an array its results.
exports.batchPromiseWrapper = function(arr) {
const filteredMungedDataArr = [];
let promiseBatachArray = arr.map(vod_id => {
var url = `https://api.twitch.tv/kraken/videos/${vod_id.id}/markers`;
var params = { api_version: 5 };
return axios
.get(url, {
params: params,
headers: {
"Client-ID": "xxxxxxxxxxxxxxx"
}
})
.then(res => {
return res.data;
})
.catch(function(error) {
console.log(error);
});
});
return Promise.all(promiseBatachArray)
.then(markers => {
if (markers !== null) {
markers.map(markerObj => {
if (markerObj.markers.game_changes !== null) {
markerObj.markers.game_changes.forEach(gameName => {
if (gameName.label === "Fortnite") {
filteredMungedDataArr.push(markerObj);
}
});
}
});
return filteredMungedDataArr;
}
})
.catch(err => {
if (err.status === 500 || err.status === 404) {
console.log("error: ", err, err.message);
}
});
};
The data looks like this:
[[1,2,3,4,5],[1,2,3,4,5]], generator will yield and make a promise.all call of 5 before pausing 5sec and continuing to the next batch of 5.
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (v, t) => {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
};
if (!evalNextValue.done) {
exports.batchPromiseWrapper(evalNextValue.value).then(data => {
let newBatchArray = batchArray;
if (data !== undefined) {
newBatchArray = batchArray.concat(data);
}
delay(5000).then(() => {
exports.batchFetchingGeneratorWrapper(generator, newBatchArray);
});
});
} else {
console.log("yay done!", batchArray);
return batchArray;
}
};
I'm able to console the results in batchArray from batchFetchingGeneratorWrapper, but I unable to act on it and I know it has something to do with async and how it has yet to be resolved.
promiseDataWrapper
.then(data => {
return gatherData.cleanUpVODData(data);
})
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
let batchArrResult = [];
let g = batchFetching(data);
new Promise((resolve, reject) => {
gatherData.batchFetchingGeneratorWrapper(g, batchArrResult);
if (g.done) { // i dont think this works
console.log("batchArrResult 1: ", batchArrResult);
resolve(batchArrResult);
}
}).then(result => console.log("asdfasdf", batchArrResult)); // empty array is returned
});
As far as I can tell, the problem lies chiefly in batchFetchingGeneratorWrapper().
It should be a matter of :
fixing delay()
making appropriate returns to make the recursion work
ensuring that the function returns Promise.
Almost undoubtedly (syntactically) simpler with async/await but here it is with old-fashioned thens :
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (t) => {
return new Promise(resolve => {
setTimeout(resolve, t);
});
};
if (!evalNextValue.done) {
return exports.batchPromiseWrapper(evalNextValue.value).then(data => {
return delay(5000).then(() => {
return exports.batchFetchingGeneratorWrapper(generator, batchArray.concat(data || []));
});
});
} else {
console.log("yay done!", batchArray);
return Promise.resolve(batchArray); // <<< promise wrapped to ensure that batchFetchingGeneratorWrapper() returns Promise
}
};
And chain the batchFetchingGeneratorWrapper() call appropriately :
promiseDataWrapper
.then(data => gatherData.cleanUpVODData(data))
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
return gatherData.batchFetchingGeneratorWrapper(batchFetching(data), []).then(batchArrResult => {
console.log('batchArrResult: ', batchArrResult);
return batchArrResult;
});
}).catch(error => {
console.log(error);
});

Nodejs How To Iterate With a Promise

I'm trying to iterate through an array of AD users and return some user information.
I've been looking for a few hours now, or more and haven't been quite able to get my head around the async nature of the activedirectory2 npm package.
I'm getting part of the result I need, however when iterating through the list of usernames, I'm only getting the first one printing out to console.
getADUser.js:
var ActiveDirectory = require('activedirectory2');
var config = require('../../conf/conf-ad.json')
var fileTime = require('./w32FiletimeToEpoch')
var moment = require('moment')
// Find user, return all
var ad = new ActiveDirectory(config);
var getADUser = function (sAMAccountName, opts) {
return new Promise(function (resolve, reject) {
ad.findUser(opts, sAMAccountName, function (err, user) {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
// return;
}
if (!user) {
console.log('User: ' + sAMAccountName + ' not found.');
} else {
if (user.userAccountControl == 514) {
user.userAccountControl = 'Disabled'
} else {
user.userAccountControl = 'Active'
}
if (user.pwdLastSet) {
user.pwdLastSet = `${moment(fileTime(user.pwdLastSet))} - ${moment(fileTime(user.pwdLastSet)).fromNow()}`
}
if (user.lastLogonTimestamp) {
user.lastLogonTimestamp = `${moment(fileTime(user.lastLogonTimestamp))} - ${moment(fileTime(user.lastLogonTimestamp)).fromNow()}`
}
if (user.lastLogon) {
user.lastLogon = `${moment(fileTime(user.lastLogon))} - ${moment(fileTime(user.lastLogon)).fromNow()}`
}
// return;
// return user.promise();
// console.log(user)
// test.push(user)
resolve(JSON.stringify(user));
}
});
})
}
module.exports = getADUser
checkADCompletions.js:
var checks = ['USERONE', 'USERTWO']
let opts = {
attributes: ['sAMAccountName', 'userAccountControl']
};
let checkADCompletions = function (userList) {
let data = []
return new Promise(function (resolve, reject) {
return new Promise(function (res, rej) {
for (let i = 0; i < userList.length; i++) {
getADUser(userList[i], opts)
.then(function (s) {
data.push(s)
}).then(function () {
resolve(data)
})
}
})
})
}
checkADCompletions(checks).then(function (d) {
console.log(d) \\ Only prints the first user details
})
You can use Promise.all like this:
let checkADCompletions = function (userList) {
var promises = userList.map(function (user) {
return getADUser(user, opts);
})
return Promise.all(promises);
}
You are basically creating an array of promises and then executing them all concurrently.
And then use it like so:
checkADCompletions(checks)
.then(function (responses) {
console.log(responses); // this will be an array
})
.catch(function (err) {
// if any of the promises fail, it will enter here.
// err will be the value of the rejected promise
})
Promise.all will fail even if just one of the checked users fail. So, you need to handle errors nicely, i.e. deal with any possible outcome of ad.findUser:
var getADUser = function (sAMAccountName, opts) {
return new Promise(function (resolve, reject) {
ad.findUser(opts, sAMAccountName, function (err, user) {
if (err || user == null) {
console.log('ERROR: ' + JSON.stringify(err));
reject(err);
}
if (user.userAccountControl == 514) {
user.userAccountControl = 'Disabled'
} else {
user.userAccountControl = 'Active'
}
if (user.pwdLastSet) {
user.pwdLastSet = `${moment(fileTime(user.pwdLastSet))} - ${moment(fileTime(user.pwdLastSet)).fromNow()}`
}
if (user.lastLogonTimestamp) {
user.lastLogonTimestamp = `${moment(fileTime(user.lastLogonTimestamp))} - ${moment(fileTime(user.lastLogonTimestamp)).fromNow()}`
}
if (user.lastLogon) {
user.lastLogon = `${moment(fileTime(user.lastLogon))} - ${moment(fileTime(user.lastLogon)).fromNow()}`
}
resolve(user);
});
})
}
A simple fix would be to call resolve only when the for loop is finished.
// checkADCompletions.js
var checks = ['USERONE', 'USERTWO']
let opts = {
attributes: ['sAMAccountName', 'userAccountControl']
};
let checkADCompletions = function (userList) {
let data = []
return new Promise(function (resolve, reject) {
for (let i = 0; i < userList.length; i++) {
getADUser(userList[i], opts)
.then(function (s) {
data.push(s)
}).then(function () {
if (i === userList.length) {
resolve(data)
}
})
}
})
})
}
checkADCompletions(checks).then(function (d) {
console.log(d)
})
When you call resolve, you close out the Promise. You're initiating two Promises and then a for loop, and you call resolve inside the for loop. So the first user gets populated but the for loop does not continue because the Promise has finished executing. Move resolve outside of the loop and you should be good to go.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve

Categories