How to do javascript promise chaining in more elegant way? - javascript

I work on my Angular2/firebase side project and stop for a while on this chunk of code. What I am trying to do here is to create a new account with signupUser method and then, only when it succeeded, call another method saveNewUserInDatabase. If I do it like underneath, method router.navigate does not wait 5 seconds.
//EXAMPLE 1
public onSignup(): void {
this.authService.signupUser(this.signupForm.value)
.then(response => {
let userObject = {
uid: response.auth.uid,
email: response.auth.email
};
setTimeout(() => this.authService.saveNewUserInDatabase(userObject) , 5000)})
.then(() => {
setTimeout(() => { this.router.navigate(['/map'])}, 1500);
})
.catch(error => {
console.log(error);
});
}
However if I nest .then method like in the example below, it works as I wish to work.
//EXAMPLE 2
public onSignup(): void {
this.authService.signupUser(this.signupForm.value)
.then(response => {
let userObject = {
uid: response.auth.uid,
email: response.auth.email
};
setTimeout(() => this.authService.saveNewUserInDatabase(userObject)
.then(() => {
setTimeout(() => { this.router.navigate(['/map'])}, 1500);
}
), 5000)
})
.catch(error => {
console.log(error)
});
}
service methods
public signupUser(user: UserLogin): firebase.Promise<FirebaseAuthState> {
return this.af.auth.createUser({email : user.email, password: user.password});
}
public saveNewUserInDatabase(user): firebase.database.ThenableReference {
return firebase.database().ref().child("users").push(user);
}
The second solution works (the user is navigated after 5 seconds to the /map path). Nonetheless I do not think that nesting promise in promise is a good practice. How can I chain promises here like
.then()
.then()
and make second .then triggered after first is completed?
Thanks for your help in advance.

You could create a delay function that you can pipe into your promise chain.
//EXAMPLE 2
function delay(time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time)
})
}
function onSignup() {
this.authService.signupUser(this.signupForm.value)
.then(response => {
const userObject = {
uid: response.auth.uid,
email: response.auth.email
}
return userObject
})
.then(this.authService.saveNewUserInDatabase)
.then(delay(5000))
.then(() => {
this.router.navigate(['/map'])
})
.catch(error => {
console.log(error)
});
}

To do as you want you need to return a promise in the first then:
public onSignup(): void {
this.authService.signupUser(this.signupForm.value)
.then(response => new Promise(function (resolve, reject) {
let userObject = {
uid: response.auth.uid,
email: response.auth.email
};
setTimeout(() => resolve(this.authService.saveNewUserInDatabase(userObject)), 5000)
})
.then( () => {
setTimeout(() => { this.router.navigate(['/map'])}, 1500)
})
.catch(error => {
console.log(error)
});
}

Related

Retry functionality in promise chain

i have a promise chain
If i receive error in getServiceCost I want to repeat the chain again (retry) for 2 times how can i achieve this when using Promise chain , meaning again execute getUser, getServiceCost
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get the user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'admin'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get the services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 3 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate the service cost of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 2 * 1000);
});
}
If i receive error in getServiceCost I want to repeat the chain again (retry) for 2 times how can i achieve this when using Promise chain , meaning again execute
getUser, getServiceCost
I'd use an async function (all modern environments support them, and you can transpile for obsolete environments), which lets you use a simple loop. Perhaps as a utility function you can reuse:
async function callWithRetry(fn, retries = 3) {
while (retries-- > 0) {
try {
return await fn();
} catch (error) {
if (retries === 0) {
throw error;
}
}
}
return new Error(`Out of retries`); // Probably using an `Error` subclass
}
Using it:
callWithRetry(() => getUser(100).then(getServices).then(getServiceCost))
.then(console.log)
.catch(error => { /*...handle/report error...*/ });
Or
callWithRetry(async () => {
const user = await getUser(100);
const services = await getServices(user);
return await getServiceCost(services);
})
.then(console.log)
.catch(error => { /*...handle/report error...*/ });

How to break chain in promise

I'm trying to limit the number of apis fetches in my project by saving them in a simple cache, key collection in mongodb. Is thera way to stop propagation of .then() inside Promise, without using async/await?
export const getData = (url: string) => {
return new Promise((resolve, reject) => {
findInCache(url)
.then((cached: string | null) => {
if (cached) {
resolve(cached);
}
})
.then(() => {
axios
.get(url)
.then(({data}) => {
setCache(url, data, TTL);
resolve(data);
})
.catch(e => reject(e));
});
});
};
Firstly, lets get rid of the Promise constructor anti-pattern - your function call inside the promise executor returns a promise, so, no need for anew Promise
Secondly, only run the second request if the result of the first is empty
export const getData = (url) => findInCache(url)
// here we return haveResult and avoid axios.get(url) altogether
.then((haveResult) => haveResult || axios.get(url)
// sometimes nested .then is useful like in this case
.then(({data}) => {
setCache(url, data, TTL);
return data;
})
);
you can just do this instead instead of chaining. if it is in cache then fetch from cache else get from url
export const getData = (url: string) => {
return new Promise((resolve, reject) => {
findInCache(url)
.then((cached: string | null) => {
if (cached) {
resolve(cached);
} else {
axios
.get(url)
.then(({data}) => {
setCache(url, data, TTL);
resolve(data);
})
.catch(e => reject(e));
}
})
});
};
When you return something result in then, this result is come into next then function. So, you can control what you would do in next then based on input parameter inCache. So you can do something like:
export const getData = (url: string) => {
return new Promise((resolve, reject) => {
findInCache(url)
.then((cached: string | null) => {
if (cached) {
resolve(cached);
return true;
}
return false;
})
.then((inCache) => {
if (!inCache) {
axios
.get(url)
.then(({data}) => {
setCache(url, data, TTL);
resolve(data);
})
.catch(e => reject(e));
}
});
});
};

is it normal to pass callback into a async function and even wrap it again?

app.js
import test from "./asyncTest";
test().then((result)=>{
//handle my result
});
asyncTest.js
const test = async cb => {
let data = await otherPromise();
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then( => response.json())
.then(json => json );
}, 2000);
};
export default test;
The fetch result "json" I intend to return is unable to be the return value of "test" function since the value only available in an inner function scope such as debounce wrapper. Since above reason, I tried to pass a callback function and wrap the callback to be Promise function(pTest) as below.
const test = async cb => {
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => cb(null, json))
.catch(err => cb(err));
}, 2000);
};
const pTest = cb => {
return new Promise((resolve, reject) => {
test((err, data) => {
if (err) reject(err);
resolve(data);
});
});
};
export default pTest;
This way works for me, but I'm wondering if it's correct or are there any ways to solve this scenario?
The fetch API already returns a promise. Wrapping it in another Promise object is actually an anti-pattern. it is as simple as the code below:
/*export*/ async function test() {
let data = await otherPromise();
return fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => {
return {
json: json,
data: data
}
});
};
function otherPromise() {
return new Promise((resolve, reject) => {
resolve('test for data value');
});
}
// In index.js call
test().then(res => {
console.log(res)
});

Passing promises through functions React Native

I'm brand new to react native and I've been browsing through snippets of code and am confused as to how promises are passed along.
I have this event handler onRefresh() that is called when I pull down on a flatlist and I'm trying to have it use the return of apiSearchDB when it returns true/false.
onRefresh = () => {
this.setState({...}, () => {
return this.apiSearchDB()
.then(function(response) {
console.log(response);
})
.catch((error) => {
console.log(error);
});
})
}
apiSearchDB = () => {
return fetch('/some_endpoint')
.then((response) => response.json())
.then((json) => {
this.setState({
...
}, () => {return true})
return true;
}).catch((error) => {
console.error(error);
return false;
})
}
The line console.log(response); only prints undefined and I can't figure out why.
Could my handler also be written as
onSearch = () => {
return new Promise((resolve, reject) => {
var response = this.apiSearchDB();
response
? resolve();
: reject();
}
});
}
or onSearch = () => {...} and function onSearch(){...}?
Thank you in advance!
You should read more about using promises (good article - We have a problem with promises). However, two basic rules that will help you in this case are are:
The value returned from a promise is wrapped in a promise.
Promises can be chained.
The apiSearchDB should return a promise that contains the json as the resolved value, and error as the rejected value:
apiSearchDB = () =>
fetch('/some_endpoint')
.then((response) => response.json())
.then((json) => json)
// can be removed unless you want to do something with the error before passing it on
.catch((error) => Promise.reject(error));
The onRefresh (or onSearch) method should get the promise from apiSearchDB, and add it's own chain. Resolve promise should be handled with the then handler. If it's the rejected value, it will be handled by the catch handler:
onRefresh = () =>
this.apiSearchDB()
.then((response) => {
console.log(response);
// do something with response
this.setState({...}, () => {
});
return response;
})
.catch((error) => {
console.log(error);
// do something with error
this.setState({...}, () => {
});
});
}

Extracting functions in a Promise chain

I am wanting to refactor a Promise chain by extracting out some functions. Currently I have
const getData = (uuid) => {
return new Promise((resolve) => {
fetch(
// go fetch stuff
)
.then((response) => {
if (!response.ok) {
return resolve(false);
}
return response;
})
.then(fetchres.json)
.then(response => {
// Do more stuff that requires resolves that I will also want to refactor
})
.catch(err => {
console.log(err);
resolve(false);
});
});
};
So I want to extract the part where I resolve the unsuccessful responses. But pass along any successful ones. I have pulled it out like so.
const resolveUnsuccessfulResponses = (response) => {
if (!response.ok) {
return response.resolve(false);
}
return response;
}
const getData = (uuid) => {
return new Promise((resolve) => {
fetch(
// go fetch stuff
)
.then(resolveUnsuccessfulResponses)
.then(fetchres.json)
.then(response => {
// Do more stuff that requires resolves that I will also want to refactor
})
.catch(err => {
console.log(err);
resolve(false);
});
});
};
Now I'm understandably getting the error resolve is not defined. How can I resolve this Promise in an external function?
Should I pass resolve to my extracted function? That would seem clunky.
.then(response => resolveUnsuccessfulResponses(response, resolve))
I might end up having something like
.then(fetchres.json)
.then(parseResponseData)
.then(postDataSomewhere)
.then(doOtherThings)
.then(doEvenMoreCoolThings)
And to have to pass response and resolve to each of them seems wrong
You should return a new Promise from your external functions aswell:
const resolveUnsuccessfulResponses = (response) => {
return new Promise((resolve, reject) => {
if (!response.ok) {
return resolve(false);
}
return resolve(response);
});
}

Categories

Resources