I have created a function verifyIfUserIsInGame(userId) that allows checking if the user given as argument is in game. This function searches the Firebase database and returns true or false. (so it is a promise within the function).
I would now like to check, once the function has been executed for 2 users if they are in play.
I'm not very comfortable with promises. I made this code but it doesn't work since I get [object Promise] in the console logs.
// We check that both users are in this game
user1IsInGame = verifyIfUserIsInGame(user1Id); // must return true or false
user2IsInGame = verifyIfUserIsInGame(user2Id); // must return true or false
console.log("user1: " + user1IsInGame);
console.log("user2: " + user2IsInGame);
if (userIsInGame && user2IsInGame) {
// The users are in game
} else {
// The users are not in game
}
});
Thanks for your help.
You can use async and await to achieve asynchronity.
async function verifyIfUserIsInGame() {
//the content of your function
return true;
}
async function func() {
// We check that both users are in this game
const user1IsInGame = await verifyIfUserIsInGame(user1Id); // must return true or false
const user2IsInGame = await verifyIfUserIsInGame(user2Id); // must return true or false
console.log("user1: " + user1IsInGame);
console.log("user2: " + user2IsInGame);
if (userIsInGame && userToKillIsInGame) {
// The users are in game
} else {
// The users are not in game
}
}
As already mentioned, you can use Promise.all, but none of the other answers had any content in your verify function, so just wanted to provide a full example.
This is one way of doing it, you can ofc change the resolve to just pass true/false back and then change the if statements.
function verifyIfUserIsInGame(user_id) {
return new Promise((resolve, reject) => {
resolve({ id: user_id, inGame: true });
});
}
// run this when all checks have been performed.
function callbackFunction(players){
let playersReady = 0;
players.forEach(player => {
if(player.inGame){
console.log(`player ${player.id} are in the game`);
playersReady++;
}
});
if(playersReady === players.length){
console.log("All players are in the game");
}
}
// the checks we want to perform
const verifyAllUsers = [
verifyIfUserIsInGame(1),
verifyIfUserIsInGame(2)
];
// wait for promises to finish.
Promise.all(verifyAllUsers)
.then( users => callbackFunction(users) )
.catch( err => console.error(err) );
Just await the promises maybe? :
(async function() {
const user1IsInGame = await verifyIfUserIsInGame(user1Id); // must return true or false
const user2IsInGame = await verifyIfUserIsInGame(user2Id); // must return true or false
console.log("user1: " + user1IsInGame);
console.log("user2: " + user2IsInGame);
if (userIsInGame && userToKillIsInGame) {
// The users are in game
} else {
// The users are not in game
}
})()
You can use Promise.all if you want to run two functions simultaneously:
(async function() {
const usersAreInGame = await Promise.all(verifyIfUserIsInGame(user1Id),
verifyIfUserIsInGame(user2Id));
if (usersAreInGame[0] && usersAreInGame[1]) {
// The users are in game
} else {
// The users are not in game
}
})()
Related
I am trying to make api call and store the results in array. function itself is an async. Here's a code.
async function setPoliciesData(policies) {
let tmpPolicies = {};
await policies.forEach((policy) => {
const tmp = { ...policy };
// Here I need help. This returns promise Instade remembers How to wait till promise finishes
tmp["Name"] = getPolicyNameFromLocalStrage(policy.id);
try {
if (policy?.audience?.id) {
tmp["members"] = getMembersFromAudienceId(policy.audience.id);
} else {
tmp["members"] = [];
}
} catch (e) {
console.log(e);
}
let id = policy.id;
console.log("Setting policy ID : " + policy.id);
tmpPolicies[policy.id] = tmp;
});
console.log("Done the processing");
return tmpPolicies;
}
I am getting Promise object in return. I would want members returnd array.
I tried to console log and I am seeing that issue seems to be because of method is not async. What is proper way to fix it.
I refactored some of your code, but if you should make the function inside of the forEach asynchronous. In this case, I changed it to map to be a bit easier to follow. The key at the end is to return Promise.all() which will wait for all of the inner promises to be resolved before returning:
async function setPoliciesData(policies) {
const tmpPolicies = policies.map(async (policy) => {
policy.Name = await getPolicyNameFromLocalStrage(policy.id);
try {
policy.members = policy.audience && policy.audience.id
? await getMembersFromAudienceId(policy.audience.id)
: [];
} catch (e) {
console.error('Error: ' + e);
}
return policy;
});
console.log("Done the processing");
return Promise.all(tmpPolicies);
}
I have my function whose job is to go over a number of files (that use the values from the array as building blocks for file names) and download them using a reduce. It's more of a hack as of now but the Promise logic should work. Except it doesn.t
Here's my code:
function import_demo_files(data) {
/**
* Make a local copy of the passed data.
*/
let request_data = $.extend({}, data);
const get_number_of_files_1 = Promise.resolve({
'data' : {
'number_of_files' : 2
}
});
return new Promise((resolve, reject) => {
let import_files = get_number_of_files_1.then(function(response) {
new Array(response.data.number_of_files).fill(request_data.step_name).reduce((previous_promise, next_step_identifier) => {
let file_counter = 1;
return previous_promise.then((response) => {
if( response !== undefined ) {
if('finished_import' in response.data && response.data.finished_import === true || response.success === false) {
return import_files;
}
}
const recursively_install_step_file = () => import_demo_file({
demo_handle: request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': file_counter
}).call().then(function(response) {
file_counter++;
if('file_counter' in response.data && 'needs_resume' in response.data) {
if(response.data.needs_resume === true) {
file_counter = response.data.file_counter;
}
}
return response.data.keep_importing_more_files === true ? recursively_install_step_file() : response
});
return recursively_install_step_file();
}).catch(function(error) {
reject(error);
});
}, Promise.resolve())
}).catch(function(error) {
reject(error);
});
resolve(import_files);
});
}
Now, when I do:
const import_call = import_demo_files({ 'demo_handle' : 'demo-2', 'step_name' : 'post' });
console.log(import_call);
The console.log gives me back that import_call is, in fact a promise and it's resolved. I very much like the way return allows me to bail out of a promise-chain, but I have no idea how to properly resolve my promise chain in there, so clearly, it's marked as resolved when it isn't.
I would like to do import_call.then(... but that doesn't work as of now, it executes this code in here before it's actually done because of the improper handling in import_demo_files.
An asynchronous recursion inside a reduction isn't the simplest of things to cut your teeth on, and it's not immediately obvious why you would want to given that each iteration of the recursion is identical to every other iteration.
The reduce/recurse pattern is simpler to understand with the following pulled out, as outer members :
1. the `recursively_install_step_file()` function
1. the `new Array(...).fill(...)`, as `starterArray`
1. the object passed repeatedly to `import_demo_file()`, as `importOptions`)
This approach obviates the need for the variable file_counter, since importOptions.file_counter can be updated directly.
function import_demo_files(data) {
// outer members
let request_data = $.extend({}, data);
const importOptions = {
'demo_handle': request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': 1
};
const starterArray = new Array(2).fill(request_data.step_name);
function recursively_install_step_file() {
return import_demo_file(importOptions).then((res) => {
if('file_counter' in res.data && 'needs_resume' in res.data && res.data.needs_resume) {
importOptions.file_counter = res.data.file_counter; // should = be += ?
} else {
importOptions.file_counter++;
}
return res.data.keep_importing_more_files ? recursively_install_step_file() : res;
});
}
// the reduce/recurse pattern
return starterArray.reduce((previous_promise, next_step_identifier) => { // next_step_identifier is not used?
let importOptions.file_counter = 1; // reset to 1 at each stage of the reduction?
return previous_promise.then(response => {
if(response && ('finished_import' in response.data && response.data.finished_import || !response.success)) {
return response;
} else {
return recursively_install_step_file(); // execution will drop through to here on first iteration of the reduction
}
});
}, Promise.resolve());
}
May not be 100% correct but the overall pattern should be about right. Be prepared to work on it some.
I have a problem with if(.isDisplayed()) else if (.isDisplayed()) else condition.
The isDisplayed() function doesn't work for this condition, it always enters in the first if block even the powderRinseStartButton is not in the DOM.
if (powderRinseStartButton != null && powderRinseStartButton.isDisplayed() && powderRinseStartButton.ispresent()) {
powderRinseStartButton.click();
} else if (brewerRinseStartButton != null && brewerRinseStartButton.isDisplayed() && brewerRinseStartButton.ispresent()) {
brewerRinseStartButton.click();
} else {
fn.click();
}
if I put the first or second piece of code, it works fine
browser.wait(() => {
return brewerRinseStartButton.isDisplayed().then(() => {
browser.wait(EC.visibilityOf(brewerRinseStartButton), delay).then(() =>
{
browser.wait(EC.elementToBeClickable(brewerRinseStartButton), delay).then(() =>
{
expect(EC.elementToBeClickable(brewerRinseStartButton)).toBeTruthy();
brewerRinseStartButton.click().then(() =>
{
browser.wait(EC.visibilityOf(maintenanceText), 240000,
'The Maintenance Text should be visible within 240s');
expect(maintenanceText.isDisplayed()).toBeTruthy();
});
});
});
});
// return powderRinseStartButton.isDisplayed().then(() =>
// {
// browser.wait(EC.visibilityOf(powderRinseStartButton), delay).then(() =>
// {
// browser.wait(EC.elementToBeClickable(powderRinseStartButton), delay).then(() =>
// {
// expect(EC.elementToBeClickable(powderRinseStartButton)).toBeTruthy();
// powderRinseStartButton.click().then(() =>
// {
// browser.wait(EC.visibilityOf(maintenanceText), 240000,
// 'The Maintenance Text should be visible within 240s');
// expect(maintenanceText.isDisplayed()).toBeTruthy();
// });
// });
// });
// });
}, 5000)
.then(() => {
console.log('return true')
return true;
}, () => {
console.log('false');
browser.wait(EC.visibilityOf(fn), delay).then(() => {
browser.wait(EC.elementToBeClickable(fn), delay).then(() => {
expect(EC.elementToBeClickable(fn)).toBeTruthy();
fn.click();
});
});
});
I want to make a condition if brewerRinseStartButton is showed than click on brewerRinseStartButton, elseif powderRinseStartButton is showed than click on powderRinseStartButton else dosomething.
I solved this problem.
The isDisplayed()
#return
A promise that will be resolved with whether this element is currently visible on the page.
Wait for all promises to be resolved, or for any to be rejected.
let failHandler = ()=>
{
browser.wait(EC.visibilityOf(fn), delay).then(() =>
{
browser.wait(EC.elementToBeClickable(fn), delay).then(() =>
{
expect(fn.isDisplayed()).toBeTruthy();
expect(EC.elementToBeClickable(fn)).toBeTruthy();
fn.click();
});
});
};
brewerRinseStartButton.isDisplayed().then(()=>
{
fnBrewer();
},()=>
{
powderRinseStartButton.isDisplayed().then(()=>
{
fnPowder();
},()=>
{
failHandler();
});
});
IsDisplayed() function returns promise, so there are two ways to handle it. One as you also attempted to use then on returning promise object and perform action afterwards.
Second one is most correct and clean way to use async/await for handling promises
async clickStartButton() {
if(powderRinseStartButton != null && await
powderRinseStartButton.isDisplayed() && await
powderRinseStartButton.ispresent())
{
await powderRinseStartButton.click();
}
else if(brewerRinseStartButton != null && await
brewerRinseStartButton.isDisplayed() && await
brewerRinseStartButton.ispresent())
{
await brewerRinseStartButton.click();
}
else
{
await fn.click();
{}
}
Note: I've used my mobile keypad for typing so please ignore the code identation
I would probably attempt to do this by using the or functionality available in xpath locators. The logic is that you provide a single locator which contains the locators required to locate both objects. If the first locator is found it will be used, if the second is found it will be used. If neither are found then you can use your ele.isDisplayed() check and run your else condition on it.
let requiredEle = element(by.xpath('//button[#class="locator1" or #class="locator2"]');
requiredEle.isDisplayed().then(function(wasDisplayed){
if(wasDisplayed){
requiredEle.click();
} else {
//Your else actions
}
})
* not tested
I would also advise using isPresent instead of isDisplayed if you can as isDisplayed throws an exception if the element is not present and I'm not exactly sure how that will behave in the above if statement.
I have tried with this code. I hope this should solve your problem. I have refactored WaitForElement code seperately so that it can be used in both the cases.
async WaitForElement(element, timeout = 30000) {
try {
await browser.wait(function () {
return element.isDisplayed()
.then((isDisplayed) => isDisplayed, () => false);
}, timeout);
await browser.wait(function () {
return element.isPresent()
.then((isPresent) => isPresent, () => false);
}, timeout);
return true;
}
catch (e) {
console.log(e.message);
return false;
}
}
async clickStartButton() {
// powderRinseStartButton = element(define locator)
//brewerRinseStartButton = element(define locator)
if (await this.WaitForElement(this.powderRinseStartButton)) {
await powderRinseStartButton.click();
} else if (await this.WaitForElement(this.brewerRinseStartButton)) {
await brewerRinseStartButton.click();
} else {
//await fn.click();
}
}
Explanation:
First element.isDisplayed() function returns Promise object so if we put it into If condition then it will always return true that's why it will always pass through first If loop.
Second point is that browser.wait understand true/false, hence we have to resolve element.isDiplayed promise so that it never through exception and we can wait until timeout.
Try the below one. This should definitely work for you.
const powderRinseStartButton= element(by.id('start_powder_rinse');
const brewerRinseStartButton = element(by.id('start_brewer_rinse');
if (await powderRinseStartButton.ispresent()) {
await powderRinseStartButton.click();
} else if (await brewerRinseStartButton.ispresent()) {
await brewerRinseStartButton.click();
} else {
await fn.click();
}
if the button takes some time to load. try adding sleep before if.
Hope it helps you. Please share the error if the approach fails.
One hack you can try for this scenario is using .then() .catch() chain of promises here instead of if else. So in then you try for clicking first element button And in catch() you do not reject or throw error but instead you click on the second element button.
So in the function you can return like below:
return brewerRinseStartButton.isDisplayed()
.then(() => brewerRinseStartButton.click())
.catch(() => {
return powderRinseStartButton.isDisplayed()
.then(() => powderRinseStartButton.click());
});
Note: I've also used my mobile keypad for typing so please ignore the code identation. Also I assumed that first you want to try clicking (if found) on brewerRinseStartButton and then you want to try clicking on powderRinseStartButton.
I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?
So I have a form, user searches a query.
Trying to do:
I make API calls consistently. The first query is entered by user, then the program makes new queries based on Entity Analysis, and makes API calls itself - over and over.
Issue: The results are the same, which means the code is making the
API calls based on the initial query over and over, instead of making
new queries from API results.
Result in Console:
queries length is zero...
Iterating... undefined
On Queue Searches #1: []
query length is zero
POST / 200 15.747 ms - -
etc. etc. etc.
code
Pushing the query entered by user into array, sending it to onQueueSearch to make API calls at a set interval.
// Global Queries
const queries = new Array();
let counter = 0; // Just to keep track
router.post('/', async (req, res) => {
// Assign first query
queries.push(req.body.searchTerm);
// Invoke the async iterator
const results = await queryIterator(queries);
res.send(results);
});
This function might be the problem?
For each query in the array, do pass it to sentimentAnalyze which calls API's, this function is suppose to make calls to API consistently at set interval. (Note: New queries are added to queries array in the API calls.)
async function onQueueSearch() {
if(queries.length === 0 ) {
console.log("queries length is zero...");
return; // return something if empty
}
// This will extract the next in queue and remove it from queries
return await sentimentAnalyze(queries.pop);
}
// Define functions for Async Iterator -----
async function* queryGenerator() {
while (true) {
yield await onQueueSearch(queries.pop());
}
}
async function queryIterator(queries) {
for await (const result of queryGenerator()) {
console.log('Iterating... ', result);
console.log(`On Queue Searches #${++counter}: `, queries);
if (!queries.length) {
console.log("query length is zero");
return result;
}
}
}
API calls function - Return a set of twitter results, then google api results - this async function try's to wait for returned promises/result
from twitter google api and returns
async function sentimentAnalyze(searchValue) {
try {
const tweetDocumentObject = await searchTwitter(searchValue);
const sentimentResults = await googleSentimentAnalyze(tweetDocumentObject);
const entitiesResults = await googleEntityAnalyze(tweetDocumentObject);
return {sentimentResults, entitiesResults};
} catch(err) {
console.log('sentimentAnalyze Error: ', err);
}
}
This function is where new queries are ADDED from entity analysis.
function googleEntityAnalyze(document) {
return new Promise((resolve, reject) => {
// Detects entities in the document
googleClient
.analyzeEntities({document: document})
.then(results => {
const entities = results[0].entities;
queries.unshift(req.body.searchTerm);
entities.forEach(entity => {
queries.unshift(entity.name);
// console.log("Entitiy name: " +entity.name);
// console.log(` - Type: ${entity.type} `);
resolve({ entity });
});
})
.catch(err => {
console.error('ENTITY ERROR:', err);
});
});
}
Ok, there's a couple of issues with your code, but easy to fix if you're carefull, I'll guide you through them.
First of:
You're pushing to queue always, this is the root cause of you getting the same data in queue:
queries.push(req.body.searchTerm); // searchTerm being '1'
... // Do more logic until googleEntityAnalyze
entities.forEach(entity => {
queries.push(entity.name);
resolve({ entity });
});
// Assume entities as [2, 3]
Now you have your queue as [1,2,3], being 1 as the first argument.
Then you redo the query logic from:
async function onQueueSearch() {
for(i = 0; i < queries.length; i++) {
return await sentimentAnalyze(queries[i]);
// queries[i].splice(i, 1);
}
if(queries.length === 0 ) {
console.log("queries length is zero...");
}
}
The return on the for loop will break the loop and return the first execution, which will be queries[0] === 1, your first argument.
So, to resolve this, keep the array in first-in-first-out, using:
queries.unshift(req.body.searchTerm);
entities.forEach(entity => {
queries.unshift(entity.name);
resolve({ entity });
});
This will keep your queries in order as they arrive [3,2,1], instead of [1,2,3], now you can extract your query using queries.pop() instead.
queries.unshift(4); // where queries was [3,2,1], will be [4,3,2,1]
queries.pop(); // 1
Change the sentiment analyze to:
async function onQueueSearch(){
if(queries.length === 0 ) {
console.log("queries length is zero...");
return; // Return something if empty!
}
return await sentimentAnalyze(queries.pop());
// This will extract the next in queue and remove it from queries
}
Secondly, your re-call logic
You're using an iterval to continuously recall the query, the problem with this:
setInterval(async function() {
results = await onQueueSearch();
}, 3000);
Is that you need to "estimate" the time the query will take to finish before re-executing. With the query unshift and pop above you'll make this work, however, you need a more elegant solution.
With NodeJS version < 10+:
recursion will be useful, here's a small sample of what you're doing in a simple manner:
const queries = [1];
let counter = 0; // Just to keep track
async function reRunQueries(){
counter++;
console.log(`queries #${counter}: `, queries);
results = await onQueueSearch();
console.log('Results: ', results);
if(!!queries.length){
return await reRunQueries();
}else{
return results;
}
}
async function onQueueSearch(){
return await longQuery(queries.pop());
}
async function longQuery(param){
if(param === 6){
// Use this as stop signal!
return param;
}else{
queries.unshift(++param);
return param;
}
}
const RES = reRunQueries();
RES.then(result => {
console.log('RES: ', result);
})
It's important to know your stop signal for recursion, otherwise it will never end.
With NodeJS > 10+
Use a Iterable Generator:
const queries = [];
let counter = 0; // Just to keep track
// EMULATE EXPRESS JS call ========================
const req = { body: { searchTerm: 1 } };
const res = {send: (val) => console.log('RECEIVED: ', val)};
const router = {
post: (route, callback) => callback(req, res)
}
router.post('/', async (req, res) => {
// Assign first query
queries.push(req.body.searchTerm);
// Invoke the async iterator
const results = await queryIterator(queries);
res.send(results);
});
// The Above is for nodeJS direct testing only,
// > you can plug it into your own router.post declaration "as is"
// -----------------------------------------
// Define functions for Async Iterator -----
async function* queryGenerator() {
while (true) {
yield await onQueueSearch(queries.pop());
}
}
async function queryIterator(queries) {
for await (const result of queryGenerator()) {
console.log('Iterating... ', result);
console.log(`On Queue Searches #${++counter}: `, queries);
if (!queries.length) {
return result;
}
}
}
// ------------------------------------------------------------
// Emulate your current async requests using queries array ----
async function onQueueSearch(param) {
return await longQuery(param);
}
async function longQuery(param) {
if (param === 6) {
// Use this as stop signal!
return Promise.resolve(param);
} else {
queries.unshift(++param);
return Promise.resolve(param);
}
}
Returns:
/*
Iterating... 2
On Queue Searches #1: [ 2 ]
Iterating... 3
On Queue Searches #2: [ 3 ]
Iterating... 4
On Queue Searches #3: [ 4 ]
Iterating... 5
On Queue Searches #4: [ 5 ]
Iterating... 6
On Queue Searches #5: [ 6 ]
Iterating... 6
On Queue Searches #6: []
RECEIVED: 6
*/