I've got two pieces of code which may or may not run when my app starts. Both produce a messageDialog, so the second must wait on the first. I'm trying to use promises to do this but I'm having issues with the returned value. Can someone point me in the right direction?
WinJS.Promise.as()
.then(function () {
// readTempFile returns a promise, see code below
if (localSettings.values["lastContent"] != 0) {
return readTempFile();
}else{
// what am I supposed to return here? false?
}
})
.then(function () {
// check for release notes
if (localSettings.values["release"] == null) {
var updates = "Updates in this version";
var msg = new Windows.UI.Popups.MessageDialog(updates, "Updates");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
}
});
function readTempFile(){
return new WinJS.Promise(function (complete, error, progress) {
// is my try / catch block redundant here?
try {
tempFolder.getFileAsync("tempFile.txt")
.then(function (file) {
file.openReadAsync().done(function (stream) {
// do stuff with the file
});
var msg = new Windows.UI.Popups.MessageDialog("unsaved work", "Warning");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
complete();
}, function () {
// file not found
error();
});
}
catch (e) {
logError(e);
error();
}
});
}
If both conditions are true, I get an access denied error. As I understand it, readTempFile() returns a promise object which my first then() statement should accept. But I'm not returning anything if the first conditional is met. I don't think that matters in this case as it just falls through to the next then, but it's not good programming.
EDIT:
Amended the readTempFile function to show that it produces a MessageDialog.
Well, let's try an analogy:
function fetchIfNotCached(){
if(!cached){
doSomething();
}
// nothing here
}
This is exactly like your case, only asynchronous. Because it utilizes promises you hook on the return value so:
what am I supposed to return here? false?
Anything you want, I'd personally probably just omit it and refactor it to if(!cached) return doSomething() in the .then. Promises chain and compose and you do not need to create them from callback interfaces unless there is a really good reason to do so.
As for readTempFile you're doing a lot of excess work as it looks like getFileAsync already returns a promise. This is a variation of the deferred anti pattern and can be rewritten as:
function(readTempFile){
return tempFolder.getFileAsync("tempFile.txt").then(function (file) {
return file.openReadAsync();
}).then(function (stream) {
// do stuff with the file, note the return as we wait for it
});
}
Found the answer. Inside the readTempFile function, I needed to add a done() to the showAsync() of my messageDialog, and put the complete() call in there. Otherwise, complete() was returning the promise while the dialog was still up.
function readTempFile(){
return new WinJS.Promise(function (complete, error, progress) {
// is my try / catch block redundant here?
try {
tempFolder.getFileAsync("tempFile.txt")
.then(function (file) {
file.openReadAsync().done(function (stream) {
// do stuff with the file
});
var msg = new Windows.UI.Popups.MessageDialog("unsaved work", "Warning");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync().done(function(){
// must be inside done(), or it will return prematurely
complete();
});
}, function () {
// file not found
error();
// have to add a complete in here too, or the app will
// hang when there's no file
complete();
});
}
catch (e) {
logError(e);
error();
}
});
}
After a couple of experiments, I figured that out myself.
As to what needed to return in my empty else statement, it was another WinJS.Promise.as()
WinJS.Promise.as()
.then(function () {
// readTempFile returns a promise, see code below
if (localSettings.values["lastContent"] != 0) {
return readTempFile();
}else{
return WinJS.Promise.as()
}
})
.then(function () {
// check for release notes
if (localSettings.values["release"] == null) {
var updates = "Updates in this version";
var msg = new Windows.UI.Popups.MessageDialog(updates, "Updates");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
}
});
I found the answer in a Google Books preview of Beginning Windows Store Application Development – HTML and JavaScript Edition By Scott Isaacs, Kyle Burns in which they showed an otherwise empty else statement returning a WinJS.Promise.as()
Related
I'm faced with a small issue when trying to chain complex function calls with Promises and callbacks.
I have a main function, which calls subroutines. In these routines API calls are made.
For Example:
function handle(){
new Promise(function(resolve, reject){
let result = doAPICall1()
if (result === true) resolve(true);
reject(JSON.stringify(result))
}).then(function(){
let result = doAPICall2()
if (result === true) return true
throw new Error(JSON.stringify(result))
}).catch(error){
console.error(JSON.stringify(error))
}
}
function doAPICall1(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
function doAPICall2(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
But when I execute this example, doAPICall2 will be executed, while doAPICall1 is still running.
It only occures when long running calls are made.
Does anyone can give me a hint? Thank you!
You're overdoing manually a lot of things that Promises already do for you:
axios.get already returns a Promise, so there is no point in return a the response when it resolves and return a false when it rejects. A catch handler at the end of a Promise chain already handles all errors that may arise during the chain, so you don't need to catch every Promise.
I would do something like:
function doAPICall1(){
return axios.get('...');
}
function doAPICall2(){
return axios.get('...');
}
function handle(){
// in case you would use the api calls results.
let firstResult = null;
let secondResult = null;
return doAPICall1()
.then(res => {firstResult = res})
.then(() => doAPICall2())
.then(res => {
secondResult = res;
return []
})
}
I guess you will use the Api calls results for something. With the code above, you could consume the handle() function like follows:
function someSortOfController(){
handle().then(results => {
console.log(results[0]); // first api call result
console.log(results[1]); // second api call result
})
.catch(err => {
// here you will find any error, either it fires from the first api call or from the second.
// there is *almomst* no point on catch before
console.log(err);
})
}
There, you will access any error, either it came from the first api call or the second. (And, due to how Promises work, if the first call fails, the second won't fire).
For more fine grained error control, you may want to catch after every Promise so you can add some extra logs, like:
function doAPICall1(){
return axios.get('...')
.catch(err => {
console.log('the error came from the first call');
throw err;
});
}
function doAPICall2(){
return axios.get('...')
.catch(err => {
console.log('the error came from the second call');
throw err;
});
}
Now, if the first api call fails, everything will work as before (since you're throwing the error again in the catch), but you have more control over error handling (maybe the error returning from API calls is not clear at all and you want this kind of control mechanism).
 Disclaimer
This answer doesn't answer why your code acts like it does. However, there are so much things wrong in your code, so I think providing you with an example about using Promises is more valuable.
Don't worry and take some time to understand Promises better. In the example code below, doAPICall function return a Promise which resolves to a value, not the value itself.
function handle() {
doAPICall().then(result => {
//do something with the result
}).catch(error => {
//catch failed API call
console.error(error)
})
}
doAPICall() {
// this returns a Promise
return axios.get(...)
}
As a node programmer. I'm used to use "nodebacks" for handling errors in my code:
myFn(param, function(err, data) {
if (err){
//error handling logic
}
else {
// business logic
}
});
When writing that function, I can do something like:
var myFn = function(param, callback){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
callback(new Error("error with calculation"), null);
}
...
someAsyncOp(calcN,function(err, finalResult){
if(err) return callback(err, null);
callback(null, finalResult); // the error is null to signal no error
});
};
How would I do this sort of error handling with promises?
Rule of Thumb
Whenever you have a doubt about how to do something with promises - think about the synchronous version.
try{
var result = myFn(param);
// business logic with result
} catch(e) {
//error handling logic
}
This, at least to me looks a lot cleaner than a callback with a first parameter that is sometimes null.
The promises way is almost always very similar to the synchronous version of the problem:
myFn(param).then(function(result){
// business logic with result
}).catch(function(e){
//error handling logic
});
Where myFn would look something like when working with callbacks:
var myFn = function(param){
return new Promise(function(resolve, reject){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
reject(new Error("error with calculation"), null);
}
someAsyncOp(calcN,function(err, finalResult){
if(err) reject(err);
resolve(finalResult);
})
});
};
Working with callbacks/nodebacks
This is only something you should have to do when working with callbacks, when working with promises it is a lot simpler, and you can do:
var myFn = function(param){
var calc = doSomeCalculation(param);
...
return someAsyncOp(calcN); // returning a promise.
}
Moreover, when working inside promise chains, you get throw safety:
myFn(param).then(function(calcN){
// here, you throw to raise an error and return to resolve
// new Promise should be used only when starting a chain.
}).catch(function(err){
// handle error
}).then(function(){
// ready to go again, we're out of the catch
});
Note, some libraries like Bluebird , RSVP and Q offer syntactic sugar and automatic promisification of methods so you rarely have to use new Promise yourself.
Also, consider reading this and that to learn more about promise error handling.
If you're using the async/await syntax, you can just use the regular try-catch syntax for error handling.
// your promise function
const myFn = function(param){
return new Promise(function(resolve, reject){
if (someLogic()) {
resolve(someValue);
} else {
reject('failure reason');
}
});
}
// Define the parent function as an async function
async function outerFn(param) {
try {
// Wait for the promise to complete using await
const result = await myFn(param)
// business logic with result
} catch (e) {
//error handling logic
}
}
I am starting to use the async await pattern more over the standard Promise syntax, as it can keep the code flatter. I have played and experimented with them a bit and thought I understood how to use them.
That has now come crumbling down!
I have a function that returns a Promise...
private async checkSecurityTokenForNearExpiry(): Promise<boolean> {
const expiryOffset = 10;
try {
let existingToken = await this.userAuthorisationService.getSecurityToken();
if (existingToken != null && !existingToken.isTokenExpired(expiryOffset)) {
return true;
}
// Attempt to get a new token.
this.logger.debug('checkSecurityTokenForNearExpiry requesting new token.');
this.getSecurityTokenWithRefreshToken().subscribe(obs => {
return true;
},
error => {
// All errors already logged
return false;
});
} catch (error) {
this.logger.error(`checkSecurityToken ${error}`);
return false;
}
}
This happens to call other function that returns a promise and also has on Observable, but all this seems to be ok.
I then call this function as follows...
this.getDataStoreValues().then(async () => {
await this.checkSecurityTokenForNearExpiry(); // <-- not waiting
requestData(); // <-- this is called before checkSecurityTokenForNearExpiry returns
...
This is inside another Promise then callback marked as async, (which should be ok?), but to my alarm the call to this.checkSecurityTokenForNearExpiry() is not finished before I see the requestData() being called. I don't need the boolean result of checkSecurityTokenForNearExpiry but added this just to see if returning something would make s difference, but it didn't.
I am lost here!
Does anyone know what I am missing here?
Thanks in advance!
async/await are working as expected but there are two factors that prevent your code from working correctly.
Observables have no special interaction with async functions. This means that they are fire and forget just as they are in normal functions. You are not awaiting getSecurityTokenWithRefreshToken but even if you did await it, it would still not behave as you want because the result would actually be the subscription returned by the call to subscribe wrapped in a Promise.
The callbacks taken as arguments by subscribe are not intended to return values so returning from them has no effect as Observable implementations will not propagate their results.
In order to make this work you need to convert the Observable into a Promise as follows
async checkSecurityTokenForNearExpiry(): Promise<boolean> {
const expiryOffset = 10;
try {
let existingToken = await this.userAuthorisationService().getSecurityToken();
if (existingToken != null && !existingToken.isTokenExpired(expiryOffset)) {
return true;
}
// Attempt to get a new token.
this.logger.debug('checkSecurityTokenForNearExpiry requesting new token.');
try {
await this.getSecurityTokenWithRefreshToken().toPromise();
return true;
} catch (error) {
return false;
}
} catch (error) {
this.logger.error(`checkSecurityToken ${error}`);
return false;
}
}
Note, if you are using RxJS you may need to add the following import
import 'rxjs/add/operator/toPromise';
I have following code snippet.
this.clickButtonText = function (buttonText, attempts, defer) {
var me = this;
if (attempts == null) {
attempts = 3;
}
if (defer == null) {
defer = protractor.promise.defer();
}
browser.driver.findElements(by.tagName('button')).then(function (buttons) {
buttons.forEach(function (button) {
button.getText().then(
function (text) {
console.log('button_loop:' + text);
if (text == buttonText) {
defer.fulfill(button.click());
console.log('RESOLVED!');
return defer.promise;
}
},
function (err) {
console.log("ERROR::" + err);
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1, defer);
} else {
throw err;
}
}
);
});
});
return defer.promise;
};
From time to time my code reaches 'ERROR::StaleElementReferenceError: stale element reference: element is not attached to the page document' line so I need to try again and invoke my function with "attempt - 1" parameter. That is expected behaviour.
But once it reaches "RESOLVED!" line it keeps iterating so I see smth like this:
button_loop:wrong_label_1
button_loop:CORRECT_LABEL
RESOLVED!
button_loop:wrong_label_2
button_loop:wrong_label_3
button_loop:wrong_label_4
The question is: how to break the loop/promise and return from function after console.log('RESOLVED!'); line?
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead.
SOURCE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
Out of curiosity what are you trying to accomplish? To me it seems like you want to click a button based on it's text so you are iterating through all buttons on the page limited by an attempt number until you find a match for the text.
It also looks like you are using protractor on a non-angular page it would be easier if you used browser.ignoreSynchronization = true; in your spec files or the onPrepare block in the conf.js file so you could still leverage the protractor API which has two element locators that easily achieve this.
this.clickButtonText = function(buttonText) {
return element.all(by.cssContainingText('button',buttonText)).get(0).click();
};
OR
this.clickButtonText = function(buttonText) {
return element.all(by.buttonText(buttonText)).get(0).click();
};
If there is another reason for wanting to loop through the buttons I could write up a more complex explanation that uses bluebird to loop through the elements. It is a very useful library for resolving promises.
You are making it harder on yourself by creating an extra deferred object. You can use the promises themselves to retry the action if the click fails.
var clickOrRetry = function(element, attempts) {
attempts = attempts === undefined ? 3 : attempts;
return element.click().then(function() {}, function(err) {
if (attempts > 0) {
return clickOrRetry(element, attempts - 1);
} else {
throw new Error('I failed to click it -- ' + err);
}
});
};
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.forEach(function(button) {
return clickOrRetry(button);
});
});
One approach would be to build (at each "try") a promise chain that continues on failure but skips to the end on success. Such a chain would be of the general form ...
return initialPromise.catch(...).catch(...).catch(...)...;
... and is simple to construct programmatically using the javascript Array method .reduce().
In practice, the code will be made bulky by :
the need to call the async button.getText() then perform the associated test for matching text,
the need to orchestrate 3 tries,
but still not too unwieldy.
As far as I can tell, you want something like this :
this.clickButtonText = function (buttonText, attempts) {
var me = this;
if(attempts === undefined) {
attempts = 3;
}
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.reduce(function(promise, button) {
return promise.catch(function(error) {
return button.getText().then(function(text) {
if(text === buttonText) {
return button.click(); // if/when this happens, the rest of the catch chain (including its terminal catch) will be bypassed, and whatever is returned by `button.click()` will be delivered.
} else {
throw error; //rethrow the "no match" error
}
});
});
}, Promise.reject(new Error('no match'))).catch(function(err) {
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1); // retry
} else {
throw err; //rethrow whatever error brought you to this catch; probably a "no match" but potentially an error thrown by `button.getText()`.
}
});
});
};
Notes :
With this approach, there's no need to pass in a deferred object. In fact, whatever approach you adopt, that's bad practice anyway. Deferreds are seldom necessary, and even more seldom need to be passed around.
I moved the terminal catch(... retry ...) block to be a final catch, after the catch chain built by reduce. That makes more sense than an button.getText().then(onSucccess, onError) structure, which would cause a retry at the first failure to match buttonText; that seems wrong to me.
You could move the terminal catch even further down such that an error thrown by browser.driver.findElements() would be caught (for retry), though that is probably overkill. If browser.driver.findElements() fails once, it will probably fail again.
the "retry" strategy could be alternatively achieved by 3x concatenatation of the catch chain built by the .reduce() process. But you would see a larger memory spike.
I omitted the various console.log()s for clarity but they should be quite simple to reinject.
I'm trying to run a function when two compilation steps are complete, but the success callback keeps getting called even one fails. Here's the code:
function compile(tplStr) {
return new Promise(function(resolve,reject) {
// compile template here
var tpl = new function(){};
resolve(tpl);
});
}
function parse(json) {
return new Promise(function(resolve,reject) {
try {
var obj = JSON.parse(json);
resolve(obj);
} catch(err) {
console.log('JSON parse failed');
reject(err);
}
});
}
var i = 0;
function bothReady() {
$('#c').text(++i);
}
function oneFailed(err) {
console.log('oneFailed hit');
$('#c').text(err.message);
}
var compileProm = compile($('#a').val());
var parseProm = parse($('#b').val());
Promise.all([compileProm,parseProm]).then(bothReady).catch(oneFailed);
$('#a').on('input', function() {
Promise.all([compile($('#a').val()),parseProm]).then(bothReady).catch(oneFailed);
});
$('#b').on('input', function() {
Promise.all(compileProm,parse($('#b').val())).then(bothReady).catch(oneFailed);
});
code pen
When I create a syntax error in the JSON portion it logs "JSON parse failed" but does not log "oneFailed hit" like I'd expect. Why not? Shouldn't the .catch block be ran if any of the promises are rejected?
Your code doesn't work correctly when something is typed inside of #b because instead of passing an iterable to Promise.All 2 parameters are passed instead.
The result is that while both promises run, only the result of the first one is taken into account by the continuation of all.
The code read
Promise.all(compileProm,parse($('#b').val())).then(bothReady).catch(oneFailed);
Instead of
Promise.all([compileProm,parse($('#b').val())]).then(bothReady).catch(oneFailed);
PS: The 2 other calls are correct it explain why the problem seem to happen only when editing the JSON.