How to wait for function to complete before returning? - intern

i'm attempting to create a function I can call from any test that'll look at the element being passed (link text, Css Selector, Xpath, ID), click on the element, and then verify the URL that comes up after it loads. The problem i'm having is that it's returning before the function is finished.
I know I need to implement async and a callback, but i'm having a hard time understanding the structure.
clickIDverifyURL: function clickByID (elementVar, elementURL){
var rem = this.remote;
// if statements to look at elementVar and select the right one.. example:
// rem.setFindByTimeout(10000)
// .findByXpath (elementVar)
// .click()
// .end()
return this.remote
// if I code it right, I shouldn't need this sleep right?
.sleep(30000)
.getCurrentUrl()
.then(function(currURL) {
console.log(currURL);
try {
assert.strictEqual(currURL, elementURL, "This test checks to see if the current URL is correct.")
}
catch (e)
{
console.log(e)
}
});
}
Appreciate any help or comments.

You're on the right track. Assuming you want to click the element, wait for a page transition, then check the resulting URL, you could do something like:
clickIDverifyURL: function (elementURL) {
return function (element) {
return this.parent
.then(function () {
return element.click();
})
// Wait for the page transition. This can be a sleep, or you can search for an
// element that should be on the new page (intern will implicitly wait for it
// to appear), or use pollUntil to wait for a more specific condition.
.sleep(1000)
// Get the page URL
.getCurrentUrl()
.then(function (url) {
assert.strictEqual(url, elementURL);
});
}
}
You would use it like:
.findElementByCssSelector('.someselector')
.then(myModule.clickIDverifyURL('expectedURL'))
clickIDVerifyURL takes in some configuration data (the expected URL) and returns a function that can be called in a Command's then callback. Such functions have a parent property in their context that references the parent Command chain (the chain of functions started from this.remote).
Note that methods called on Elements, like element.click() above, return Promises, not Commands. This means that only standard Promise methods can be chained, not Command methods like click, findElementByX, etc. That's why the code above starts the inner chain from this.parent rather than element.
Update
The same basic structure works for other types of helper method. For example, if you wanted to use a helper method to do a find, it might look like:
findBySomething: function (selector) {
return function () {
var setContext = arguments[arguments.length - 1];
return this.parent
.findByCssSelector(selector)
.then(function (element) {
setContext(element);
});
}
}
Then you could do
this.remote
.then(myModule.findBySomething('.selector'))
.then(myModule.clickIDverifyURL('expected URL'))

Related

Async Await Behaviour

A simple code to understand async/await is making me crazy.
I have a button on click on which i am reading some value from localstorage and showing it in an alert box. Before showing the alert i want to console.log the value.
If i understood async/await my code should d exactly that but it is working in reverse order. first the alert is coming and then the console.
//Method called on button click
findMyAge2() {
this.getData("age").then(result => {
console.log('Hi');
myAge2 = result;
});
alert(myAge2);
}
async getData(key): Promise<any> {
return await this.storage.get(`${key}`);
}
Expected Result:
in Console log :Hi
UI: Age
Actual Results:
UI: Age
in Console log :Hi
JavaScript is asynchronous by nature, so when you have a Promise returned, code continues execution and that Promise resolves some time afterward.
async/await is a way to control this flow of programming and can allow you to create Synchronous code that "awaits" the result of asynchronous execution.
Your problem here is that you want to return the promise from your getData function and await it in your findMyAge2 function.
async function findMyAge2() {
let result = await this.getData("age");
console.log('Hi');
myAge2 = result;
alert(myAge2);
}
async getData(key): Promise<any> {
return this.storage.get(`${key}`);
}
By adding async keyword to a function it will always now return a Promise. So you do not need to await in your return statement. That effectively does nothing. It would be like saying:
function getData(key) {
return new Promise(resolve => {
return localStorage.get(`${key}`).then(result => resolve(result))
})
}
You don't need to await that result in local storage because the consumer should be calling await or .then on the result regardless.
Try this way,
findMyAge2() {
this.getData("age").then(result => {
console.log('Hi');
myAge2 = result;
alert(myAge2);
});
}
async getData(key): Promise<any> {
return await this.storage.get(`${key}`);
}
Before you should wait to alert before data being retrieved.
async/await works like with promises, which means, code gets executed and you don't know when it will finish, you just know that after if finishes, you want something else to be executed.
This is exactly what you put inside the "then" function, which is what gets executed after the 1st part (asyc getData) gets executed.
In the case of your code, the findMyAge2 gets called when you click the button, then it executes the getData and specifies what happens after getting the result from that call, within the "then" block.
Therefore you just have to move the alert, into "then" block, then you'll have the result you expect.
var myAge2;
findMyAge2() {
this.getData("age").then(result => {
console.log('Hi');
myAge2 = result;
alert(myAge2);
});
}
async getData(key): Promise<any> {
return await this.storage.get(`${key}`);
}

Avoid callback multi-invocation when forEach is used

I have a function that processes an array of data (first parameter) and, once the procesing is finished, it invokes only one time a callback function (second parameter). I'm using forEach to process data item by item, consisting the processing of each item in some checkings and storing the param in database. The function storeInDB() does the storing work and uses a callback (second parameter) when the item has been stored.
A first approach to the code is the following:
function doWork(data, callback) {
data.forEach(function (item) {
// Do some check on item
...
storeInDB(item, function(err) {
// check error etc.
...
callback();
});
});
}
However, it's wrong, as the the callback function will be invoked several times (as many as element in the data array).
I'd like to know how to refactor my code in order to achieve the desired behaviour, i.e. only one invocation to callback once the storing work is finished. I guess that async could help in this task, but I haven't find the right pattern yet to combine async + forEach.
Any help is appreciated!
You can use a library such as async to do this, although I would recommend using promises if possible. For your immediate problem you can use a counter to determine how many storage calls have completed and call the callback when the total number are completed.
let counter = 0;
data.forEach(function (item) {
// Do some check on item
...
storeInDB(item, function(err) {
// check error etc.
counter++
if (counter == data.length) {
callback();
}
});
});
you can also utilize the three parameters passed to the function to execute on each array method
function doWork(data, callback) {
data.forEach(function (value,idx,arr) {
// Do some check on item
...
storeInDB(arr[idx], function(err) {
// check error etc.
...
if ( (idx + 1) === arr.length ) {
callback();
}
});
});
}
If storeInDB function returns a promise, you can push all async functions to an array and use Promise.all. After all tasks run successfully, It will invokes callback function.
Hope this helps you.
function doWork(data, callback) {
let arr = [];
data.map(function(itm) {
// Do some check on item
...
arr.push(storeInDB(item));
});
Promise.all(arr)
.then(function(res) {
callback();
});
}

Basic node.js - variable and Mongoose scope

the console.log(workingWeekdaysVar) line; is outside the findOne's scope, and the variable was declared outside it too, yet it's giving me null ...
when i put console.log(workingWeekdaysVar); inside the findOne's scope, it does give the right output, but this is useless for me because I wanna use workingWeekdaysVar elsewhere below.
The two commented out lines are the 2nd approach i attempted to do, but it gave me an undesirable output because this whole code is inside a complicated for loop.
How can I simply pass the fetched value of workingWeekdaysVar out of the scope?
var workingWeekdaysVar = [];
buyerSupplierFisModel.findOne(filter).then(function (combo) {
workingWeekdaysVar = combo.workingWeekdays;
//server.getWorkingWeekdays = function () { return combo.workingWeekdays };
});
console.log(workingWeekdaysVar);
//console.log(server.getWorkingWeekdays());
findOne() is an asynchronous function (it returns a promise object). This means that it returns inmediately and your next code line is run (in this case, console.log(workingWeekdaysVar);. But, since the function isn't done yet, workingWeekdaysVar is empty, and it will be empty until findOne() has done its job and returned the results in the provided chained callback .then(function (combo) {....
So if you want to do anything with the results, you'll have to do it in the callback. One option to this would be to use async / await:
(async () => {
try {
const { workingWeekdaysVar } = await buyerSupplierFisModel.findOne(filter)
console.log(workingWeekdaysVar)
} catch (e) {
console.log(`Error: ${e}`);
}
})()
Re-arranging your code a bit:
let doToResponse = (combo)=>{
workingWeekdaysVar = combo.workingWeekdays;
console.log(workingWeekdaysVar);
}
buyerSupplierFisModel.findOne(filter).then(function (combo) {
doToResponse(combo)
});
good for re-usability
My personal favorite:
buyerSupplierFisModel.findOne(filter).then(combo=> {
workingWeekdaysVar = combo.workingWeekdays;
console.log(workingWeekdaysVar);
});
The important thing is keep in mind, as Miguel Calderón says.. findOne - returns a promise. At that point you have another thread with different local (Lexical?) scope

Chaining Promises in Office.js with Excel.run()

I’m working with the new office.js. I’m using the Excel.run functionality that returns a promise. I have a question about the promises pattern implemented by the library.
The samples all show this pattern
Excel.run( function (ctx) {
//set up something
return ctx.sync().then (function () {
//call another function somewhere to chain operations
});
}).then ( function () {
//do something else if you want
}).catch (function (error) {
handle errors
});
The problem is the ctx.sync().then() contained within Excel.run()
The way it is presented, you can’t chain promises in accordance with the promises spec because you lose the context object if you try and handle the then() outside of Excel.run()
So, the pattern seems to be promoting nested function calls, which is what promises are supposed to eliminate.
What I want to do is sequence several calls together through chaining like this:
Excel.run( function (ctx) {
return ctx.sync();
}).then ( function (ctx) {
return ctx.sync();
}).then ( function (ctx) {
return ctx.sync();
}).then ( function (ctx) {
return ctx.sync();
}).catch (function (error) {
});
Is this possible?
In general, the purpose of Excel.run is for a sequential operation against the OM with automatic cleanup at the end. That is, Excel.run creates a context, runs you operation, and then cleans up any host objects that were allocated.
That being said, as mentioned by Gab Royer, you can pass objects out. And moreover, each Excel object has a back-pointer to its "context" via the ".context" property. So for example, you can do this:
Excel.run(function (ctx) {
var worksheet = ctx.workbook.worksheets.getActiveWorksheet();
return ctx.sync(worksheet);
}).then(function(worksheet) {
worksheet.name = "Test"
return worksheet.context.sync();
}).catch(function(e) {
console.log(e)
});
As you can see, in the code above, you had created the worksheet object inside the Excel.run, but are using it outside.
If you have something like a Range object, it gets a little trickier. Ranges, unlike Worksheets, do not have persistent IDs (How could they? There is essentially a countless number of permutations of all possible combinations of cells). Instead, during Excel.run, we automatically create persistent pointers to the backing Range objects that get adjusted and kept-track-of by Excel. When the batch inside of Excel.run completes, we tell the host to destroy these references. So if you had code like this:
Excel.run(function (ctx) {
var range = ctx.workbook.getSelectedRange();
return ctx.sync(range);
}).then(function(range) {
range.format.fill.color = "red";
return ctx.sync();
}).catch(function(e) {
console.log(e)
})
It would run into an "InvalidObjectPath" error.
However, you can opt out of the tracked-object cleanup by manually adding the object to the ctx.trackedObjects collection. In doing this, however, you are taking it upon yourself to clean up at the end -- and you need to be extra careful, to remember to cleanup on not only on success, but on failure. Otherwise, you're essentially creating a memory leak that will keep slowing down the Excel host application.
var range;
Excel.run(function (ctx) {
range = ctx.workbook.getSelectedRange();
ctx.trackedObjects.add(range);
return ctx.sync(range);
}).then(function(range) {
range.format.fill.color = "red";
return range.context.sync();
}).then(function() {
// Attempt to clean up any orphaned references
range.context.trackedObjects.remove(range);
range.context.sync(); // don't need to await it, since it's just the final cleanup call
}).catch(function(e) {
console.log(e);
})
Long story short: it is certainly doable, and you can use objects after Excel.run. You'll just need to be responsible for memory-management for any objects that require "tracking". In the example above, there is no reason to go through this effort, since you could just as well have had the same code inside of Excel.run (remember, you can chain promises within the batch inside of Excel.run, too -- no need to do this on the outside). But if you have a scenario, where, say, you have a timer job that needs to run every so often (e.g., to update a stock ticker), or you want to create a button with an onclick handler for a particular object, etc. the technique above will let you create the objects inside of Excel.run, and then use them outside of it.
PS: With regards to the pattern requiring nesting: It is true that if you need to chain ctx.sync() calls within Excel.run, you will end up with a layer of nesting -- but just a single extra layer. Within, you still would still be able to chain your promises without the callback pyramid. E.g.,:
Excel.run(function (ctx) {
var range = ctx.workbook.worksheets.getActiveWorksheet().getRange("A1:C3");
range.load("values");
return ctx.sync()
.then(function () {
// Some set of actions against the OM, now that the "values"
// property has been loaded and can be read from the "range" object.
})
.then(ctx.sync)
.then(function () {
// Another set of actions against the OM, presumably after doing
// another load-requiring operation (otherwise could have
// been part of the same .then as above)
})
.then(ctx.sync)
.then(function() {
// One final set of actions
});
}).catch(function(error) {
console.log("Error: " + error);
});
While this would be possible since Excel.RequestContext.sync takes in a pass-through value, the goal of Excel.run is to manage trackedObjects for the function that it gets passed in. In promises chained after Excel.Run you would have to manage trackedObjects yourself, hence defeating the purpose of Excel.Run.
I'd suggest either declaring your function outside the Excel.Run if you don't like the added indentation, or creating your own RequestContext object.

Node.js, Synchronize.js and return values

I'm using this wonderful sync module, synchronize.js - http://alexeypetrushin.github.io/synchronize/docs/index.html.
I've run into a situation where I have to get the return value of the sync'd function into the scope outside of the fiber. Here's a basic example of what I'm talking about:
var records = sync.fiber(function() {
var results = ... // some synchronized function
return results;
});
Whereas records would, in theory, contain the value of resultsfrom within the fiber scope. I've been reading up on futures (fibers/futures module) and how they might be used in this situation but I have yet to come up with anything close to working. I'd love some direction and/or a solution.
edit:
For a more thorough example of what I'm looking to accomplish:
// executes a stored procedure/function
exec: function (statement, parameters) {
init();
var request = new sql.Request(),
results;
processParams(parameters, request);
var res = sync.fiber(function(){
try {
var result = sync.await(request.execute(statement, sync.defers('recordsets', 'returnValue')));
results = result.recordsets.length > 0 ? result.recordsets[0] : [];
return results;
}
catch (e) {
console.log('error:connection:exec(): ' + e);
throw(e);
}
});
// though typical scope rules would mean that `results` has a
// value here, it's actually undefined.
// in theory, `res` would contain the return value from the `sync.fiber` callback
// which is our result set.
return res;
}
As you can see here, what I'd like to accomplish is to get the value of results in the primary scope, from the fiber's scope.
Now it does support it, use following form
var records = sync.fiber(function() {
var results = ... // some synchronized function
return results;
}, function(err, results){... /* do something with results */});
It's not a scope problem. This wont work because return res; executes before the fiber returns. That is why it's undefined.
You need to rewrite your exec function to take a callback. Then you could use synchronize.js on the exec function itself.

Resources