How to get promise value correctly? - promise

Consider the following code:
function toolsQueryResult(){
const query = `query{......}`
return request('http://...', query,).then(data => { return data })
}
var toolsQueryResult= toolsQueryResult();
var toolsNames = [];
toolsQueryResult.then(function(result){
result['key'].forEach(function(item){
toolsNames.push(item["name"])
})
})
console.log(toolsNames)
This returns and prints out empty list "[ ]" to me.Does any one know why?
But if I put "console.log()" between two final "})", it returns list of tools correctly.How should I work with this promise object to have list of tools correctly after second "})" at the end of code?

The reason is, your console.log statement is executed before the the promise toolsQueryResult is resolved. It would be really useful and helpful if you have debugger tools on and place breakpoints to see what i just said.
That said,having the console.log outside of the promise being resolved or rejected beats the whole purpose, meaning you are trying to output a statement before it could complete its execution, hence when you place the console.log statement inside of the then function it outputs result.
Fiddle with your code (modified the result to be a simple array) for you to debug and see : https://jsfiddle.net/jayas_godblessall/vz8mcteh/
or execute it here to see :
function toolsQueryResult() {
const query = `query{......}`
return request('http://...', query, ).then(data => {
return data
})
}
// just to emulate your api call
function request(foo) {
return Promise.resolve(["item1", "item2"]);
}
var toolsQueryResult = toolsQueryResult();
var toolsNames = [];
toolsQueryResult.then(function(result) {
result.forEach(function(item) {
toolsNames.push(item)
})
console.log("am executed after you waited for promise to complete - in this case successfully, so you can see the tool sets")
console.log(toolsNames)
})
console.log("am executed before you could resolve promise")
console.log(toolsNames)

Related

Cypress returning Synchronous value within Async command?

So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.
Here is my function:
searchCustomer(searchText: string) {
this.customerInput.type(searchText)
this.searchButton.click()
cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length > 0) {
{Click some drop downdowns }
return data < ----I think here is the problem
} else {
{Do other stuff }
}
})
}
and in my test case itself:
let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)
So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)
Cypress basically told me I was wrong via the error message:
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then
returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue
cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another
cy.then().
So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?
Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.
Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.
searchCustomer(searchText: string): Chainable<any[]> {
this.customerInput.type(searchText)
this.searchButton.click()
return cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length) {
{Click some drop downdowns }
return data
} else {
{Do other stuff }
return []
}
})
}
// using
searchCustomer('my-customer').then((data: any[]) => {
if (data.length) {
}
})
Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.
It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.
you just need to add return before the cy.wait
here's a bare-bones example
it("test", () => {
function searchCustomer() {
return cy.wait(100).then(intercept => {
const data = {text: "my data"}
return data
})
}
const myCustomer = searchCustomer()
myCustomer.should("have.key", "text")
myCustomer.its("text").should("eq", "my data")
});

How do I refactor a traditional synchronous loop with RxJS?

I'm new to RxJS and trying to wrap my brain around how I should be writing my code. I'm trying to write a function that extends an existing http which returns an observable array of data. I'd like to then loop over the array and make an http request on each object and return the new array with the modified data.
Here's what I have so far:
private mapEligibilitiesToBulk(bulkWarranties: Observable<any[]>): Observable<IDevice[]> {
const warranties: IDevice[] = [];
bulkWarranties.subscribe((bulk: any[]) => {
for (let warranty of bulk) {
// Check if another device already has the information
const foundIndex = warranties.findIndex((extended: IDevice) => {
try {
return warranty.device.stockKeepingId.equals(extended.part.partNumber);
} catch (err) {
return false;
}
});
// Fetch the information if not
if (foundIndex > -1) {
warranty.eligibilityOptions = warranties[foundIndex];
} else {
this.getDevices(warranty.device.deviceId.serialNumber).subscribe((devices: IDevice[]) => {
warranty = devices[0];
}); // http request that returns an observable of IDevice
}
warranties.push(warranty);
}
});
return observableOf(warranties);
}
Currently, my code returns an observable array immediately, however, its empty and doesn't react the way I'd like. Any advice or recommended reading would be greatly appreciated!
Without knowing a lot more about your data and what would make sense, it is impossible to give you the exact code you would need. However, I made some assumptions and put together this StackBlitz to show one possible way to approach this. The big assumption here is that the data is groupable and what you are actually trying to achieve is making only a single http call for each unique warranty.device.stockKeepingId.
I offer this code as a starting point for you, in the hopes it gets you a little closer to what you are trying to achieve. From the StackBlitz, here is the relevant method:
public mapEligibilitiesToBulk(bulk: Warranty[]): Observable<IDevice[]> {
return from(bulk).pipe(
tap(warranty => console.log('in tap - warranty is ', warranty)),
groupBy(warranty => warranty.device.stockKeepingId),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], []))),
tap(group => console.log('in tap - group is ', group)),
concatMap(group => this.getDevices(group[0].device.deviceId.serialNumber)),
tap(device => console.log('in tap - got this device back from api: ', device)),
toArray()
)
}
A couple of things to note:
Be sure to open up the console to see the results.
I changed the first parameter to an array rather than an observable, assuming you need a complete array to start with. Let me know if you want this to extend an existing observable, that is quite simple to achieve.
I put in some tap()s so you can see what the code does at two of the important points.
In the StackBlitz currently the getDevices() returns the same thing for every call, I did this for simplicity in mocking, not because I believe it would function that way. :)

Protractor dealing with promises and arrays in flow control

I'm working on some Jasmine end-to-end testing, using Protractor test runner. The application I am testing is a simple webpage. I already have a test scenario that works fine.
Now I'd like to improve my code so that I can use the same script to run the testing scenario twice.
The first time: the test would be performed on the English version of the page
The second time: on a translated version of the same page.
Here is my code:
var RandomSentenceInThePage = ["Sentence in English", "Phrase en Francais"];
var i;
var signInButton;
var TranslationButton;
var RandomSentenceInThePageBis;
i = 0;
//Runs the testing scenario twice
while (i < 2) {
describe('TC1 - The registration Page', function() {
//the translation is done on the second iteration
if (i != 0) {
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('https://Mywebsite.url.us/');
//we get the translation button then click on it
TranslationButton = element(by.css('.TranslationButtonClass'));
TranslationButton.click();
});
}
//On the first iteration, we run the test on the not translated page…
Else {
beforeEach(function() {
browser.ignoreSynchronization = true; //Necessary for the browser.get() method to work inside the it statements.
browser.get('https://Mywebsite.url.us/');
});
}
it('should display the log in page', function() {
//Accessing the browser is done in the before each section
signInButton = element(by.css('.SignInButtonClass'));
signInButton.click();
RandomSentenceInThePageBis = element(by.css('.mt-4.text-center.signin-header')).getText();
/*******************[HERE IS WHERE THE PROBLEM IS]*******************/
expect(RandomSentenceInThePageBis.getText()).toEqual(RandomSentenceInThePage[i]);
});
/*******************************************************************/
});
}
I have highlighted the problematic section. The code keeps running even before the comparison between RandomSentenceInThePage[i] and RandomSentenceInThePageBis are compared. And when they are finally compared, the loop is already done.
According to what I have seen on the other related topics, because of the use of expect statements and getText() methods, I am dealing with promises and I have to wait for them to be resolved. After trying for the whole day, I think I could use a hint on how to deal with this promise resolution. Let me know if you need more information.
Change while loop to for loop and declare the variable: i by let, rather than var
let can declare variable at code block scope like for, if block etc. But var can't.
Because protractor api execute async, thus when the expect()... execute for the second time. the value of i has become 2, not 1
for(let i=0;i<2;i++) {
describe('TC1 - The registration Page', function() {
....
})
}

Need correct call to Promise reduce (when.reduce )

I have a processor function that takes a "cmd" object and returns a promise where the resolution is the same "cmd" object passed in (with a response key added). reduce here is when.reduce
reduce = require('when').reduce;
//return processor(cmds[0])
return reduce(cmds, function(processor, cmd) {
Debug.L1('running processor for component ', cmd.component)
return processor(cmd)
})
.then(cmds => {
Debug.L1('cmds with responses\n', cmds)
let response = cmds.map(cmd => {
return cmd.response
})
console.log('the complete response is\n', response)
});
This does nothing, it does get to the .then but the array of promises never fires, never see the Debug running processor...
If I run just a single processor it works great cmd[0], cmds[1], etc.
return processor(cmds[0])
//return reduce(cmds, function(processor,cmd) {
// Debug.L1('running processor for component ', cmd.component)
// return processor(cmd) })
What am I missing here? Their api and wiki examples aren't giving me any insight.
IMPORTANT UPDATE:
The answer below does work but throws unhandled rejection errors. The culprit is the when library. It seems no longer active and has not been updated since node 6. I switched to bluebird and it works fine without any change to the code outlined below.
I'm still not sure what you are looking for, but it might be
reduce(cmds, function(responses, cmd) {
return processor(cmd).then(function(response) {
responses.push(response); // or whatever
return responses;
});
}, []).then(function(responses) {
…
});
Before trying to understand when.reduce, you might want to have a look at the non-promise array reduce.

Sequelize correctly executing multiple creates + updates

I have a cron job that scrapes a list of items on a website and then inserts or updates records in a database. When I scrape the page, I want to create records for new ones that haven't been created yet, otherwise update any existing ones. Currently I'm doing something like this:
// pretend there is a "Widget" model defined
function createOrUpdateWidget(widgetConfig) {
return Widget.find(widgetConfig.id)
.then(function(widget) {
if (widget === null) {
return Widget.create(widgetConfig);
}
else {
widget.updateAttributes(widgetConfig);
}
});
}
function createOrUpdateWidgets(widgetConfigObjects) {
var promises = [];
widgetConfigObjects.forEach(function(widgetConfig) {
promises.push(createOrUpdateWidget(widgetConfig));
});
return Sequelize.Promise.all(promises);
}
createOrUpdateWidgets([...])
.done(function() {
console.log('Done!');
});
This seems to work fine, but I'm not sure if I'm doing this "correctly" or not. Do all promises that perform DB interactions need to run serially, or is how I have them defined ok? Is there a better way to do this kind of thing?
What you're doing is pretty idiomatic and perfectly fine, the only room for improvement is to utilize the fact Sequelize uses Bluebird for promises so you get .map for free, which lets you convert:
function createOrUpdateWidgets(widgetConfigObjects) {
var promises = [];
widgetConfigObjects.forEach(function(widgetConfig) {
promises.push(createOrUpdateWidget(widgetConfig));
});
return Sequelize.Promise.all(promises);
}
Into:
function createOrUpdateWidgets(widgetConfigObjects) {
return Sequelize.Promise.map(widgetConfig, createOrUpdateWidget)
}
Other than that minor improvement - you're chaining promises correctly and seem to have the correct hang of it.

Resources