Chaining Promises Cross Function Javascript - javascript

I suspect I've fundementally misunderstood Javascript promises, any ideas?
I have a pretty function that queries a database containing music that looks like this:
function searchDatabaseForTrack(query,loadedResults){
loadedResults = loadedResults || [];
desiredResults = 100;
if (loadedResults.length < desiredResults) {
try {
databaseApi.searchTracks(query, {"offset":loadedResults.length, "limit":"50", }).then(function(data){
i=0
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
for (thing in data.tracks.items){
loadedResults.push(data.tracks.items[i]);
i=i+1;
}
console.log(loadedResults.length, " tracks collected");
searchDatabaseForTrack(query,loadedResults)
}
});
} catch(err) {
console.log("ERROR!", err)
console.log(loadedResults)
return loadedResults;
}
} else {
console.log(loadedResults)
return loadedResults;
}
}
And then a bit later, I try to call and use the data retrieved.
function getArtistTracks(artistName){
searchDatabaseForTrack(artistName).then(function(data){
console.log(songs);
songs.sort(function(a,b){
var c = new Date(a.track.album.release_date);
var d = new Date(b.track.album.release_date);
return d-c;
});
console.log("songs", songs);
var newsongs=[];
i=0
for (song in songs) {
newsongs.push(songs[i].track.uri);
i++
};
return newsongs;
});
}
What I'm trying to do is get the second function "getArtistTracks" to wait for the completion of the query in the first function. Now I could just call the databaseApi.searchTracks directly, but there's a limit of 50 tracks returned per result — which kind of screws me over.

searchDatabaseForTrack().then(...) shouldn't work since searchDatabaseForTrack() doesn't return a promise, so you can either return a promise or use an async function.
instead of a recursive function, you could simply call databaseApi in a for loop,
the desiredResult should be an argument and not hardcoded in the function,
async function searchDatabaseForTrack(query, desiredResults){
let loadedResults = [], data, currOffset = 0;
const iterations = Math.ceil(desiredResults / 50);
for(let n = 0 ; n < iterations; n++){
cuurOffset = n * 50;
data = await databaseApi.searchTracks(query, {"offset":currOffset, "limit":"50", });
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
loadedResults = loadedResults.concat(data.tracks.items);
console.log(loadedResults.length, " tracks collected");
}
}
return loadedResults;
}
the rest should be fine as long as you add .catch() to handle errors ( as mentionned in previous answer ) which are thrown automatically without the need of the try/catch block :
function getArtistTracks(artistName, 100){
searchDatabaseForTrack(artistName).then((songs) => {
// your previous code
})
.catch((err) => {
// handle error
});
});

Have searchDatabaseForTrack use Promise.all to return the loadedResults after all results have been gotten. Also, make sure not to implicitly create global variables as you're doing with thing. For example, try something like this:
async function searchDatabaseForTrack(query) {
const desiredResults = 100;
const trackPromises = Array.from(
({ length: Math.ceil(desiredResults / 50) }),
(_, i) => {
const offset = i * 50;
return databaseApi.searchTracks(query, { offset, limit: 50 });
}
);
const itemChunks = await Promise.all(trackPromises);
const loadedResults = itemChunks.reduce((a, { tracks: { items }}) => (
[...a, ...items]
), []);
return loadedResults;
};
and
searchDatabaseForTrack(artistName).then((loadedResults) => {
// do stuff with loadedResults
})
.catch((err) => {
console.log("ERROR!", err)
// handle error
});

Related

How do I assign a variable defined in a synchronous scope in an async function?

I am trying to write a GUI frontend that uses a service to get data about the system. I am using a net.Socket for the client end of this. I want to be able to access certain variables assigned in the data event handlers in other modules but the assignment does not stay after that callback function finishes.
Problematic code:
client.on('data', (data) => {
var array = [...data];
array.splice(0,2);
for (var i=0;i<array.length;i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
module.exports.hasBattery = lastBatteryJSON.hasBattery == 'true';
module.exports.isCharging = lastBatteryJSON.isCharging == 'true';
module.exports.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
}
dataInBuffer = '';
});
Those three exported variable assignments don't actually work, the variables always either stay undefined or their default values outside of the function. I tried using a Promise to solve this problem but got the same result. I'm at a loss and I can't find any other questions or forum posts that solve this problem.
EDIT
I do not have the option of moving the code that depends on those variables into the callback. In order to do that I would have to wait for the data every frame and flood the server as a result.
As apple commented; you can export an object and mutate it every time you receive data:
const data = {};
client.on('data', (data) => {
var array = [...data];
array.splice(0, 2);
for (var i = 0; i < array.length; i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
//mutate the data object
data.hasBattery = lastBatteryJSON.hasBattery == 'true';
data.isCharging = lastBatteryJSON.isCharging == 'true';
data.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
}
dataInBuffer = '';
});
//export the data object
module.exports.batteryData = data;
Or as CertainPerformance answered you can have the caller decide when to ask for the information and provide a promise.
Here is an extended version of CertainPerformance answer that listens to error as well so a promise can be rejected and cleans up the event listeners when promise is resolved or rejected:
//wrapper for client.on to add and remove event listeners
const listeners = (function(){
var listenerCounter = -1;
const listeners = [];
const triggerEvent = event => data =>{
listeners.filter(
listener=>listener[2] === event
).forEach(
listener=>listener[1](data)
);
};
client.on('data', triggerEvent("data"));
client.on('error', triggerEvent("error"));//assuming you have an error event
return {
add:(event,fn)=>{
listenerCounter = listenerCounter + 1;
if(listenerCounter>1000000){
listenerCounter=0;
}
listeners.push([listenerCounter,fn,event]);
return listenerCounter;
},
remove:num=>{
listeners = listeners.filter(
listener=>{
num !== listener[0];
}
)
}
}
}());
//convert data to object or false
const getObjectFromData = data => {
var array = [...data];
var dataInBuffer="";
array.splice(0,2);
for (var i=0;i<array.length;i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
return {
hasBattery : lastBatteryJSON.hasBattery == 'true',
isCharging : lastBatteryJSON.isCharging == 'true',
lastBatteryReading : parseFloat(lastBatteryJSON.batteryLife)
};
}
return false;
}
//export this function
const getBatteryData = () =>
new Promise((resolve,reject) => {
const removeListeners = ()=>{
listeners.remove(okId);
listeners.remove(errorId);
}
const okId = listeners.add(
"data",
data=>{
const resultObject = getObjectFromData(data);
if(resultObject){
resolve(data);
removeListeners();//clean up listeners
}else{
//not sure of on data is triggered multiple times by client.on.data
// if it is then at what point do we need to reject the returned promise?
}
}
)
const errorId = listeners.add(
"error",
error=>{
reject(error);
removeListeners();//clean up listeners
}
)
});
//you can call getBatteryData like so:
//getBatteryData()
// .then(batteryData=>console.log(batteryData))
// .catch(error=>console.warn("an error getting battery data:",error))
Your module should export a function that returns a promise that returns the desired values. Also, use const and not var when possible:
let resolveObj;
const haveData = new Promise((resolve) => {
let resolved = false;
client.on('data', (data) => {
const array = [...data];
array.splice(0, 2);
for (let i = 0; i < array.length; i++) {
dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
}
console.log(dataInBuffer);
if (dataInBuffer.startsWith('batStat')) {
const {
hasBattery,
isCharging,
batteryLife,
} = JSON.parse(dataInBuffer.split(';')[1]);
resolveObj = {
hasBattery: hasBattery === 'true',
isCharging: isCharging === 'true',
lastBatteryReading: Number(batteryLife),
};
if (!resolved) resolve();
resolved = true;
}
dataInBuffer = '';
});
});
const getData = () => haveData.then(() => resolveObj);
module.exports = getData;
Then consume with
moduleFunction().then(({ hasBattery, isCharging, lastBatteryReading }) => {
// do something with results
});
If called before resolveObj is populated, the promise will wait until the first client.on('data' to resolve. After that, the function will return a promise that resolves immediately to the current value of resolveObj (which will be properly updated on client.on('data')

NodeJS REST API wait for response

I am trying to get a bunch of ID's from an API and then form a sequence of requests which would make further calls to an API to fetch some parameters. These would be totaled and i expect the output results to be pushed as JSON array.
The problem is REST call is async and i've put a promise but not sure when to resolve the promise back to the calling function, the rest call some times take a second or 2 to respond back.
I would like know at what point can i resolve the promise or how to know when the totals have been computed ?
The Route
app.get("/sonar/:x_id",function(req,resp) {
getRestSonar(req.params.x_id).then(function (fromResolve) {
resp.send(fromResolve);
});
});
The function with promise which makes the rest call loops
var getRestSonar = function(requestX) {
return new Promise(function(resolve,reject) {
var unirest = require("unirest");
var reqx = unirest("GET", "http://sonarqubexxServer/api/projects");
var outputJson = {
table: []
};
reqx.end(function (res) {
if (res.error) throw new Error(res.error);
// console.log(res.body);
var result = res.body;
//var needle = req.params.csi_id;
var needle = requestX;
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
for (var i=0;i<result.length;i++) {
if (result[i].nm.indexOf(needle) !== -1) {
console.log(result[i].k);
var queryUrl = "http://sonarqubexxServer/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
console.log(queryUrl);
var subrequest = unirest("GET",queryUrl);
subrequest.end(function (resXX) {
if (resXX.error);
var resXXResult = resXX.body;
for (var i=0;i<resXXResult.length;i++) {
// var duplicateData = resXXResult[0].msr.filter(item => item.key == 'duplicated_lines');
resXXResult[i].msr.forEach(m => {
if (m.key === 'duplicated_lines') {
console.log('Duplicated Lines ' + m.val);
TotalDuplicateLines += m.val;
}
else if(m.key === 'bugs' ) {
console.log('Bugs ' + m.val);
TotalBugs += m.val;
}
else if(m.key === 'ncloc' ) {
console.log('Lines of Code ' + m.val);
TotalNcloc += m.val;
}
else if(m.key === 'code_smells' ) {
console.log('Code Smells ' + m.val);
TotalCodeSmells += m.val;
}
else if(m.key === 'vulnerabilities' ) {
console.log('Vulnerabilities ' + m.val);
TotalVulnerabilities += m.val;
outputJson.table.push({totduplines:TotalDuplicateLines},{totVul:TotalVulnerabilities});
}
});
console.log("Iam here with I :: " + i);
if (i === (resXXResult.length - 1)) {
//Should i resolve here makes no sense
console.log("Resolved the promise now..");
}
//The for ends here
}
// I see this is a bad place to resolve..
resolve(outputJson);
});
}
}
});
});
}
EDIT : As suggested in the comments, split the calls into smaller
sections
Now, i fetch the api calls seperatly create an array out of it, then use promises to call back to the API ? how do i resolve each call by looping over it ?
When i try to loop it always resolves request[0] and then comes out of the promise, how can i create a promise array and wait for them to complete ?
app.get("/sonar/:csi_id",function(req,resp) {
var collectiveResult = [];
getRestSonar(req.params.csi_id).then(function (fromResolve) {
return splitReqUrl(fromResolve);
}).then(function(fromSplitUrl) {
console.log("I am from split url ::::" + fromSplitUrl);
return getSubSonarProperties(fromSplitUrl);
}).then(function(fromsubSonar) {
collectiveResult.push(fromsubSonar);
console.log("+++++++++++++++++++++++++++");
console.log(fromsubSonar);
resp.send(collectiveResult);
});
});
var getSubSonarProperties = function(getUrl) {
return new Promise(function(resolve,reject) {
var getSubRest = require("unirest");
console.log("Attempting to GET " + getUrl);
var req = getSubRest("GET",getUrl);
var outputJson = {
table: []
}
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
req.end(function (res) {
if (res.error);
var resXXResult = res.body;
resolve(resXXResult);
});
});
}
var splitReqUrl = function(request) {
return new Promise(function(resolve,reject) {
resolve(request[1]);
//for(var i=0; i< request.length; i++) {
// resolve(request[i]);
//}
});
}
var getRestSonar = function(requestX) {
return new Promise(function(resolve,reject) {
var unirest = require("unirest");
var reqx = unirest("GET", "http://sonarqubexxx/api/projects");
var outputJson = {
table: []
};
reqx.end(function (res) {
if (res.error) throw new Error(res.error);
// console.log(res.body);
var result = res.body;
//var needle = req.params.csi_id;
var needle = requestX;
var queryArray = [];
for (var i=0;i<result.length;i++) {
if (result[i].nm.indexOf(needle) !== -1) {
console.log(result[i].k);
var queryUrl = "http://sonarxxx/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
//console.log(queryUrl);
queryArray.push(queryUrl);
}
if (i === (result.length - 1)) {
resolve(queryArray);
}
}
});
});
}
Problem
First of all the problem with your solution is that you're trying to make everything inside a single big new Promise(...) creator.
Even if you manage to make that work it's still a common anti-pattern as Promises are made to be chained using the .then(...) method.
As pointed out by Roamer-1888 there oughta be a fork of unirest that handles Promises directly instead of requiring callbacks as in your example, but let's stick with your version of unirest here.
Solution
So what you need to be doing is create a Promise chain to handle the different steps of your code and pass the results down the chain.
Your steps seem to be:
Make the first call to retrieve initial results.
Filter the results based on the requestX input.
For each item left, make several calls to obtain more data.
Put everything back into an outputJson object.
Basically the only async steps are 1 and 3, but it might be ok to add a third step to build your outputJson and pass it downstream.
So let's start with the first step.
1. Make the first call
In the first link of the Promise chain we need to retrieve the initial results with your first unirest call:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
See in this example I already checked if the response contains an error and fired a rejection in that case, otherwise I resolve the promise with the body (the data we need).
The Promise we created above will throw an error if the request fails, and will downstream the body of the response if everything goes fine.
2. Filtering and Sub-calls
Now then we can go ahead and use the full potential of Promises with the .then(...) method:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
}).then((results) => {
results = results.filter((result) => {
return result.nm.indexOf(request) != -1;
});
return Promise.all(results.map((result) => {
return new Promise((resolve, reject) => {
var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
unirest("GET", queryUrl)
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
}))
})
In this step I used some Array methods to make the code cleaner and Promise.all to handle several promises together.
Array.filter is a method which iterates an array and checks for each item if it should be kept in the filtered output or not. So, in your case, we want to keep only those items where result.nm.indexOf(request) != -1.
Array.map is a method which iterates an array and converts each item to something else. Basically the function you provide takes each item as input, converts it to something else and then replaces this new value to the old one in the output array.
Finally Promise.all accepts an array of Promises and returns a Promise itself. This returned Promise will resolve when all the given Promises resolve and will pass downstream an array which items are the results of each single Promise.
So by writing Promise.all(results.map((results) => { return new Promise(...) })) we convert each result in the results array into a Promise that executes the result-specific call and put it into the output array of Promises which is fed to Promise.all so they get executed at once.
3. Build the outputJSON
Now the Promise chain outputs the result of Promise.all which is an array of all the results of each Promise, which are the results of each sub-call.
We can then simply take the downstream data and use your nested iterations to build the outputJSON to be passed downstream:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
}).then((results) => {
results = results.filter((result) => {
return result.nm.indexOf(request) != -1;
});
return Promise.all(results.map((result) => {
return new Promise((resolve, reject) => {
var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
unirest("GET", queryUrl)
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
}))
}).then((allResults) => {
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
var outputJson = {
table: []
};
for (var i = 0; i < allResults; i++) {
for (var j = 0; j < allResults[i].length; j++) {
allResults[i][j].msr.forEach(m => {
if (m.key === 'duplicated_lines') {
TotalDuplicateLines += m.val;
}
else if (m.key === 'bugs') {
TotalBugs += m.val;
}
else if (m.key === 'ncloc') {
TotalNcloc += m.val;
}
else if (m.key === 'code_smells') {
TotalCodeSmells += m.val;
}
else if (m.key === 'vulnerabilities') {
TotalVulnerabilities += m.val;
outputJson.table.push({ totduplines: TotalDuplicateLines }, { totVul: TotalVulnerabilities });
}
});
}
}
return outputJson;
})
If your return this long Promise chain in your getRestSonar(request) function, then you could write getRestSonar(request).then((outputJson) => { ... do something with your outputJson ... })

NodeJS Async - Use it like for-cycle

How to use NodeJS Async (https://caolan.github.io/async/) as normal for-cycle?
for(var i = 0; i < 100; i++){
doSomething();
}
I need serial flow but normal for-cycle not waiting until action is finished.
I am open to ES6 solutions, if any.
You can try to use Async/Await from ES6 which is cleaner and common shared in many standards. And in addition you don't need any dependencies from third party
const forLoopiNSeria = async () => {
for(var i = 0; i < 10; i++){
console.log(await doSomething(i))
}
}
function doSomething(index) {
return new Promise( (resolve, reject) => {
setInterval(() => resolve(index), 500)
})
}
forLoopiNSeria()
I believe you could always do something like this:
function asyncWhile(condition, action, ctx) {
const whilst = function(data) {
return condition.call(ctx, data) ?
Promise.resolve(action.call(ctx, data)).then(whilst) :
data;
}
return whilst();
}
let i = 1
asyncWhile(
() => {
if (i <= 100) {
i += 1
return true
}
return false
},
() => {
console.log(`iteration ${i}`)
},
)
I found another way with usage of until function from async:
var i = 0;
var max = 100;
async.until(() => {
i++;
return i === max-1;
}, function (callback) {
//doMagic()
callback();
}, function () {
console.log("Finished");
});

How to create a new promise?

Well I understand that one would have to return a promise, and not the result of a promise to pass promises around.
However I seem to be unable to implement this, say I have a member method like:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return true;
}
};
return () => {CreateThem(counter, maxNum);};
}
Basically it creates orders in a recursive fashion, terminating after finding this.MaxRequests empty spots and then puts them together in a list under this.orders
Now I called this function by:
initialLoader.CreateNextBatch().then(function (success) {
console.log(success);
console.log("loaded");
initialLoader.initializeBatch();
});
However this fails:
TypeError: initialLoader.CreateNextBatch(...).then is not a function
at LoadExternDatabase...
Why isn't this working? What am I not understanding yet?
Edit: I've also tried to replace the return by a new promise:
return new Promise((resolve, reject) => {
CreateThem(counter, maxRequests);
resolve();
});
However this executes the resolve immediatelly, instead of waiting for CreateThem to complete. Nor directly the function by return CreateThem.bind(this, counter, maxRequests);
You should resolve a promise after the asynchronous operation done.
For example:
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Resolved here!
// Because asynchronous function `setTimeout` ends here
resolve();
}, 1000);
})
}
In your code, if r >= 0, it will find an entry in database again until r less than 0, so it ends at else block:
if (r >= 0) {
// ...
} else {
// ends here
}
Maybe you could change your code to:
CreateNextBatch() {
return new Promise((resolve, reject) => {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
reject(err);
});
} else {
resolve(true);
}
};
});
// return () => {CreateThem(counter, maxNum);};
}
return new Promise((resolve, reject) => {
globalVarKeep = resolve;
globalVarBreak = reject;
});
Before that you would have had some code waiting for a promise or an event that says: -
CreateThem(counter, maxRequests);
globalVarKeep();
or
CreateThem(counter, maxRequests).then(globalVarKeep);
If that's appropriate.
The function initialLoader.CreateNextBatch() returns a function, not a promise, you should do: initialLoader.CreateNextBatch()()
The function CreateThem can return true if this.maxNum >= 0 and a promise of true or false depending on the recursion. But doesn't allways guaranteed return a promise. And () => {CreateThem(counter, maxNum);}; doesn't return anything either. You could try the following:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
//you can return a promise here
return Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return Promise.resolve(true);
}
};
//you are not returning CreateThem here
return () => {return CreateThem(counter, maxNum);};
}

How to loop a promise in for loop and break if condition met

I am new to using Promise and breaking my head to solve this. I want to run a script until a condition is met in protractor with below code but it's not working. how to fix this?
let counter = 30;
for (let i = 0; i < counter; i++) {
browser.executeScript(somescript).then((value) => {
//console.log(value);
if (value > 0)
//do some stuff
}
else {
return; //exit for loop --not working
//or
i=counter;// to exit if i and counter value same
}
})
}
I have this limitedRetry code that would appear to be what you need
limitedRetry = (cont, fn) => fn().catch(err => cont > 0 ? limitedRetry(cont - 1, fn) : Promise.reject(err));
Your use would be
limitedRetry(30, function() {
browser.executeScript(somescript).then((value) => {
if (value > 0) {
// do some stuff
return 'this will be the resolved value of the promise';
} else {
return Promise.reject('try again');
}
});
})
.then(v => console.log(v))
.catch(err => console.log(err));
Now you're firing all executeScripts at the same time, and in the worst case the last script (result) may return first. You should start the next executeScript only if you have the result of the previous executeScript.
This can be done by 'listening' to the result of the previous promise, and calling executeScript after you know what the previous result was. I've replaced somescript with the variable i, so that the result is more determinate. In case you're just looking for a retry, see the other answer.
var browser = {
executeScript: function(i) {
return Promise.resolve(i);
}
}
var initial = Promise.resolve(null);
for (var i = 0; i < 30; i++) {
(function(locali) {
initial = initial.then(function(prev) {
if(prev === undefined) return;
return browser.executeScript(locali).then(function (value) {
console.log(value);
if (value < 5) {
return value;
} else {
return;
}
});
});
})(i);
}

Categories

Resources