javascript Convert standard function to a promise - javascript

I have a standard function that successfully checks to see if a particular app is installed. But that function takes a few moments to execute and I need to bind it into a promise.then(function (response) { ...} because the app checker takes too long to execute...causing an async issue with the intended response from the app check function. But I can't get it work.
checkSocialApp only returns a true or false:
function checkSocialApp(objApp) {
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
return true ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
return true ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
return false ;
}
);
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;
The above errors out on the .then. So I try to bind it to a promise.resolve:
var promise1 = Promise.resolve(checkSocialApp(appObj)) ;
promise1.then(function(response) {
if (response == true) ...
This executes the checkSocialApp function successfully (as I see proper console messages printing from within that function), but I am not getting the response back into the remaining part of the .then for processing.

You have to do something like this return a promise in your function:
function checkSocialApp(objApp) {
return new Promise( function(resolve)={
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
resolve (true) ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
resolve (true) ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
resolve (false) ;
}
);
}
})
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;

Does checkSocialApp usually take a callback? You can wrap it in a promise like this:
function checkSocialAppPromise ( appObj ) {
return new Promise( function ( resolve ) {
checkSocialApp( appObj, resolve )
});
}

What you have should work, strictly speaking. If checkSocialApp(appObject) returns true or false, then you should get it. What you show works. If it isn't, then there must be something odd going on.
const someFunc = () => 5;
Promise.resolve(someFunc()).then(result => console.log(result));
However, it probably won't do what you are trying to do. You can't magically make a long-running synchronous function run async.
For example, if I have this function:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
runSlow();
console.log('done');
Which takes 5 seconds to complete. I can't just wrap it up and make it asynchrnous suddenly:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
Promise.resolve(runSlow())
.then(response => console.log(response));
While technically it is wrapped in a promise, it still takes 5 seconds to run and it still blocks things (try clicking while it's running and you'll see your browser is unresponsive).
If you want it to run properly, you'll have to modify the function itself to stop being synchronous all together. There isn't any way to just wrap it up in it's own thread or anything.

Related

Why function is executed although await is used?

I have used await keyword in the main function to wait for the completion of async function call to poll() and yet the function call to my_plot is made before the completion of the poll() function.
async function main() {
getParametersData()
await poll()
my_plot()
}
async function getData() {
const response = await fetch(API)
const message = await response.json()
return message
}
async function poll(count = 1) {
console.log(`Polling ${count}`);
try {
const data = await getData();
if (data && Object.keys(data).length !== 0) {
console.log("Poll", data)
return;
} else {
setTimeout(poll, 5000, ++count);
}
}
catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
setTimeout(poll, 5000, 1);
}
}
async function my_plot() {
console.log("my plot")
}
Code output:
Polling 1
my plot
Polling 2
Polling 3
Poll [1,2,3]
Expected:
Polling 1
Polling 2
Polling 3
Poll [1,2,3]
my plot
Don't use setTimeout directly from within an async function. Instead, use a Promise-based wrapper.
It's surprising that modern ECMAScript doesn't come with an in-box Promise-based version of setTimeout, but it's straightforward to implement:
function delay( timeout ) {
if( typeof timeout !== 'number' || timeout < 0 ) throw new Error( "Timeout must be a non-negative integer milliseconds delay value." );
return new Promise( function( resolve ) {
setTimeout( resolve, timeout );
});
}
Then you can rewrite your poll function with a "real" while loop, like so (below).
I think your poll function should return a true/false value to indicate success or failure to the caller, if you ever need to.
Consider using typeof instead of less safe checks like Object.keys(data).length - or at least using a typeof check before using Object.keys.
Though annoyingly typeof null === 'object', so you will always need a !== null check, grumble...
As an alternative, consider having your own type-guard function (yes, I know this isn't TypeScript), that way you get even stronger guarantees that data contains what you need (as JS does not have static type checking).
async function poll( count = 1 ) {
console.log(`Polling ${count}`);
let i = 0;
do {
try {
const data = await getData();
if( isMyData( data ) ) {
return true;
}
}
catch( err ) {
console.error( err );
}
console.log( "Polling again in 5 seconds." );
await delay( 5000 );
i++;
}
while( i < count );
console.log( `Gave up after ${count} attempts.` );
return false;
}
// Type-guard:
function isMyData( data ) {
return (
( typeof data === 'object' )
&&
( data !== null )
&&
( 'this is my object' in data )
&&
( data['there are many like it but this one is mine'] )
&&
( data.myJavaScriptEngineIsMyBestFriend )
&&
data.itIsMyLife
&&
data.withoutMe_javaScriptIsUseless
&&
data.withoutJavaScript_iAmUseLess > 0
);
}
Note that if you intend to catch errors thrown by getData you should use a minimally scoped try instead of having more logic in there, as generally you won't want to catch unrelated errors.
Using the answer from How to make a promise from setTimeout, you can use a traditional loop.
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
async function poll() {
for (let count = 1;; count++) {
console.log(`Polling ${count}`);
try {
const data = Math.random(); // simulated data
if (data < 0.2) { // simulated 20% success rate
console.log("Poll", data)
return data;
} else {
console.log("Retrying in 5 seconds");
await later(5000);
}
} catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
count = 1;
await later(5000);
}
}
}
async function main() {
console.log("Start");
await poll();
console.log("Poll done");
}
main();

How to wait for promises to be resolved

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
}
})()

Intercept fetch() request, put it on-hold and resume when certain criteria is met

The following code fires an alert when it detects fetch() request to a certain endpoint. Doing so makes the request stops from proceeding, waits for the user to close the alert and then lets the request flow to the endpoint.
My question is how to achieve the same interruption, but instead of waiting for the alert to be closed, I'd need the request to wait for the appearance of a cookie. I have a feeling it needs to be done with Promises :)
const x = window.fetch;
window.fetch = function() {
if (arguments[0] == '/certain_endpoint') { alert('stopping for a while'); }
return x.apply(this, arguments)
}
You can use setInterval with promises to periodically poll for a certain condition and resolve when it is met.
const x = window.fetch;
window.fetch = function() {
if (arguments[0] == '/needs_cookie') {
return waitForCookie().then(cookie => {
return x.apply(this, arguments);
});
} else {
return x.apply(this, arguments);
}
}
// Returns a promise that resolves to a cookie when it is set.
function waitForCookie() {
return new Promise(function (resolve, reject) {
var intervalID = setInterval(checkForCookie, 100);
function checkForCookie() {
var cookie = getCookie();
if (cookie) {
clearInterval(intervalID);
resolve(cookie);
}
}
});
}
// Here, getCookie() returns undefined the first two times it's called.
// In reality, it should just parse document.cookie however you normally do.
var attempts = 0;
function getCookie() {
if (attempts < 2) {
attempts++;
console.log('Attempts: ', attempts);
return undefined;
} else {
return 'cookie!';
}
}
If you're trying to do more complicated asynchronous stuff, including polling, you may want to check out RxJS.
{
const original = window.fetch
window.fetch = function(...args) {
if (args[0] == '/certain_endpoint') {
return new Promise(res => {
setTimeout(() => res(original(...args)), 1000);
});
}
return original(...args);
};
}
You may return a promise instead that resolves after some time

Javascript function runs both routes

Still have problems with this. When I call this function it appears to return twice??? I get 'test2' in the console followed by 'test'. I also put the callback return value into the console and its false then true. The getwidget is only called once from this.nextwidget which is a button.
getwidget = function (callback) {
var id = get_name_value('salesperson');
var password = 'password';
if (id !== "") {
$.get('http://somewhere.com?id=' + id + '&password=' + password,
function (data, status) {
console.log(status);
if (status === 'success') {
catalogue = $.parseJSON(data);
if (catalogue.status === "error") {
alert(catalogue.message);
} else {
console.log('test');
if (callback) callback(true);
}
} else {
alert("Error connecting to API:" + status);
}
});
}
console.log('test2');
if (callback) callback(false);
};
this.nextwidget = function() {
catindex = catindex + 1;
getwidget(function(trigger) {
if (!trigger && catindex > 0) {
catindex = catindex - 1;
}
if (catindex === catlen - 1) {
document.getElementById("nextwidget").disabled = true;
}
if (catindex !== 0) {
document.getElementById("prevwidget").disabled = false;
}
console.log(catindex);
console.log(trigger);
});
};
Javascript is asynchronous.
$.get operation is asynchronous, because it is a call to the server.
It means that JS is not waiting till the end of this operation and just continuing excuting next block of code.
So, according to your code, it shows 'test2' in a console, than executes callback with false parameter. And only when response from server received, it shows "test" in console and run callback with true parameter.
(certainly, if request was successful, according to your snippet)
Here is simple jsfiddle for better understanding how it works.
Simple async example
function asyncExample() {
setTimeout(function() {
console.log('test');
}, 1000);
console.log('test2')
}
asyncExample();
Because operation in timeout is delayed (like asynchronous), JS will not wait and just continue with next lines. So the result will be:
'test2' first and then 'test'.

Best practice to avoid clashing promises in js

I have a save function in my app which can be called manually and an autosave function which runs every 60 seconds.
To prevent the two ops trying to access the same file at the same instant, I set a flag called isSaving to true when one starts running, and to false again afterward. If open or save detect that autosave is running, they wait 1000ms and try again. If they fail after that I consider it an error.
Autosave:
setInterval(autosave, 1000 * 60);
isSaving = false;
function autosave()
{
return new WinJS.Promise(function (complete, error, progress)
{
if(isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// saving flag off
isSaving = false;
complete();
});
}
else {
// silently abort
complete();
}
});
}
Manual save:
var saveFileAttempts = 0;
function save()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// show notification to user "file saved"
return showSaveNotification()
})
.then(function () {
// set saving flag off
isSaving = false;
complete();
});
}
else if (saveFileAttempts < 10) {
// try again in 1000ms, up to 10 times
saveFileAttempts++;
setTimeout(function () { save(); }, 1000);
}
else{
error();
}
});
}
Open:
var openFileAttempts = 0;
function open()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false)
{
return readFile()
.then(function (file) {
currentFile = file;
openFileAttempts = 0;
complete();
});
}
else if (openFileAttempts < 10) {
// try again in 1000ms, up to 10 times
openFileAttempts++;
setTimeout(function () { open(); }, 1000);
}
else{
error();
}
});
}
This feels like a hack. Is there a better way to achieve what I'm trying to do?
FYI: These functions return promises because there are other functions that call them.
Instead of waiting 1000ms and trying again, I'd recommend using a promise to represent that a save is ongoing and when it will end.
var saving = null;
setInterval(function() {
if (!saving) // ignore autosave when already triggered
save().then(showAutoSafeNotification);
}, 60e3);
function save() {
if (saving)
return saving.then(save); // queue
// else
var written = writeFile(currentFile);
saving = written.then(function() {
saving = null;
}, function() {
saving = null;
});
return written;
}
You can do the same with open (and might want to abstract the written part out), although I fail to see how it interferes with an (auto)save. If you're concerned about reading the file that is already open while it is saved, I'd let the filesystem handle that and catch the error.
how about maintaining a single promise chain. Then, you might not need a setTimeout, this is a easy way, might be flawed, haven't used WinJS, writing code like it is normal promise:
setInterval(save, 1000 * 60);
var promise = Promise.resolve();
function save(){
return promise.then(function(){
return writeFile(currentFile);
});
}
function open(){
return promise.then(function(){
return readFile().then(function (file) {
currentFile = file;
});
});
}
but I guess, one problem with this code is, since it is single promise chain, you need to catch error properly in your application.

Categories