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...*/ });
Related
im trying to write a promise but seems to be missing something. here is my code:
const myPromise = new Promise(() => {
setTimeout(() => {
console.log("getting here");
return setinputs({ ...inputs, images: imageAsUrl });
}, 100);
});
myPromise
.then(() => {
console.log("getting here too");
firebase.database().ref(`collection/${idNode}`).set(inputs);
})
.then(() => {
console.log("all is set");
})
.catch((err) => {
console.log(err);
});
if i run the program, the first part of the promise is executing but all .then() functions arent executing. how do i fix this?
In this scheme, the promise callback has one (resolve) or two (resolve,reject) arguments.
let p = new Promise((resolve, reject)=> {
//do something
//resolve the promise:
if (result === "ok") {
resolve(3);
}
else {
reject("Something is wrong");
}
});
p.then(res => {
console.log(res); // "3"
}).catch(err => {
console.error(err); //"Something is wrrong
});
Of course, nowadays you can use async + await in a lot of cases.
You need to resolve the promise, using resolve() and also return the promise from firebase so the next .then in the chain works properly.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("getting here");
// You have to call resolve for all `.then` methods to be triggered
resolve({ ...inputs, images: imageAsUrl });
}, 100);
});
myPromise
.then((inputs) => {
console.log("getting here too");
// You have to return a promise in a .then function for the next .then to work properly
return firebase.database().ref(`collection/${idNode}`).set(inputs);
})
.then(() => {
console.log("all is set");
})
.catch((err) => {
console.log(err);
});
I have this following piece of code
new Promise((resolve, reject) => {
resolve(apiRequest(data))
reject(console.log('Error'))
}).then(response)
Both methods (resolve and reject) are being fired but I want to call reject only when something goes wrong.
How can I throw an error if something goes wrong on that case?
I checked that but it seems like I can not use an If statement to do that check.
new Promise((resolve, reject) => {
const printResult = apiRequest(data)
console.log(printResult) //Outputs Promise {<pending>}
resolve(printResult) //Then it works
reject(console.log('Error'))
}).then(response)
What would be the correct approach to reject a promise?
The easiest way would be with an if condition. i.e
new Promise((resolve, reject) => {
// do something...
if(somethingGoodHappened) {
resolve(data)
} else {
reject(error)
}
})
But usually when dealing with async requests, the thing you are calling will often be returning a promise, so you can attach the then and catch callbacks there.
apiRequest(data)
.then((result) => {
// all good
})
.catch((err) => {
console.log(err)
})
const mock_api = () => new Promise((res, rej) => {
const number = Math.floor((Math.random() * 100) + 1);
setTimeout(() => {
if (number%2==0) return res('randomly RESOLVED')
return rej('randomly REJECTED')
}, 2000)
})
const async_promise = () => new Promise(async (resolve, reject) => {
try {
const resolvedPromise = await mock_api()
resolve(resolvedPromise)
} catch (e) {
reject(e)
}
})
const classicPromise = () => new Promise((resolve, reject) => {
mock_api()
.then(resolve)
.catch(reject)
})
const makeAsyncRequest = async () => {
try {
const data = await async_promise()
console.log('ASYNC AWAIT RESOLVE', data)
} catch (e) {
console.log('ASYNC AWAIT ERR', e)
}
}
makeAsyncRequest()
classicPromise()
.then(r => console.log('PROMISE CHAIN RESOLVE', r))
.catch(e => console.log('PROMISE CHAIN ERR', e))
Because of you resolve before reject so it cannot run into reject,
You can use:
if (printResult) {
resolve(printResult)
} else {
reject(console.log('Error'))
}
You can catch exceptions and return them as rejected Promises
function asyncFunc() {
try {
doSomethingSync();
return doSomethingAsync()
.then(result => {
ยทยทยท
});
} catch (err) {
return Promise.reject(err);
}
}
Always check for err if there is any err return a promise (example below)
// Return new promise
return new Promise(function(resolve, reject) {
// Do async job
request.get(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
})
})
I have a situation where I think the only choice for me is to nest some Promises within each other. I have a Promise that needs to be performed and a method that does something until that Promise is complete. Something like this:
let promise = new Promise((resolve, reject) => {
// Do some stuff
});
doSomethingUntilPromiseisDone(promise);
However, within my Promise, I need to execute another method that returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
// Do something here
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
But now, in the fetchValue method's then statement, I have another method I need to execute that, guess what, returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
saveToCache(value)
.then((success) => {
console.log('success!!');
resolve('success');
});
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
So in the end, I have a Promise, within a Promise, within a Promise. Is there someway I can structure this better so that it is more straightforward? It seems like nesting them within each other is counter to Promise's intended chaining approach.
Use .then()
let doStuff = (resolve, reject) => {/* resolve() or reject() */};
let promise = new Promise(doStuff);
doSomethingUntilPromiseisDone(
promise
.then(value => fetchValue(url))
.then(value => value.blob())
.then(saveToCache)
)
.then(success => console.log("success!!"))
.catch(err => console.error(err))
you can use generator to flatten your nested promises (Bluebird.couroutine or Generators)
//Bluebird.couroutine
const generator = Promise.coroutine(function*() {
try {
const value = yield fetchValue(url);
const success = yield saveToCache(value);
console.log('success:', success);
} catch(e) {
console.error(err);
}
}));
generator();
Each function will call the next one with the result of the method before.
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);
I have a Promise.all that executes asynchronous functions mapped on an array input if it's not null and then resolve data to a previously defined Promise:
Promise.all((inputs || []).map(input => {
return new Promise((resolve, reject) => {
someAsyncFunc(input)
.then(intermediateOutput => {
someOtherAsyncFunc(intermediateOutput )
.then(output => {
return Promise.resolve(output )
})
.catch(reason=> {
return Promise.reject(reason)
})
})
.catch(reason => {
return Promise.reject(reason);
})
})
.then(outputs => {
resolve(outputs)
})
.catch(reason => {
reject(reason)
})
}))
I only get empty outputs before even someAsyncFunc finishes its work. How can make Promise.all wait for the promises inside to finish their asynchronous work ?
Would not just
return Promise.all((inputs || []).map(input =>
somePromiseFunc(input).then(someOtherPromiseFunc)
);
work ?
You're not using Promise.all right the first time since it takes an array of promises as input, and not (resolve, reject) => { ... }
Promise.all is going to be rejected as soon as one of the underlying promises fails, so you don't need to try to do something around catch(error => reject(error)
Example:
const somePromiseFunc = (input) => new Promise((resolve, reject) => {
setTimeout(() => {
if (input === 0) { reject(new Error('input is 0')); }
resolve(input + 1);
}, 1000);
});
const someOtherPromiseFunc = (intermediateOutput) => new Promise((resolve, reject) => {
setTimeout(() => {
if (intermediateOutput === 0) { reject(new Error('intermediateOutput is 0')); }
resolve(intermediateOutput + 1);
}, 1000);
});
const f = inputs => {
const t0 = Date.now()
return Promise.all((inputs || []).map(input => somePromiseFunc(input).then(someOtherPromiseFunc)))
.then(res => console.log(`result: ${JSON.stringify(res)} (after ${Date.now() - t0}ms)`))
.catch(e => console.log(`error: ${e} (after ${Date.now() - t0}ms)`));
};
f(null)
// result: [] (after 0ms)
f([1, 0])
// error: Error: input is 0 (after 1001ms)
f([1, -1])
// error: Error: intermediateOutput is 0 (after 2002ms)
f([1, 2])
// result: [3,4] (after 2002ms)
See jfriend's comment.
someAsyncFunc and someOtherAsyncFunc are function that properly return a promise
with something like return new Promise(/*...*/);
this is useless:
.then(output => {
return Promise.resolve(output )
})
read the Promise documentation
same
.catch(reason=> {
return Promise.reject(reason)
})
the Promise is already rejecting, you don't need to catch and reject yourself
to make sure Promises are chainable you need to return the Promise
// ...
return new Promise((resolve, reject) => {
if(inputs == null)
resolve([]);
else {
Promise.all(inputs.map(input => {
return someAsyncFunc(input)
.then(someOtherAsyncFunc)
}))
.then(resolve)
.catch(reject)
}
});
note I would rather not make the arry for Promise.all inline, it adds visual clutter:
return new Promise((resolve, reject) => {
if(inputs == null)
resolve([]);
else {
const myPromises = inputs.map(input => {
return someAsyncFunc(input)
.then(someOtherAsyncFunc)
});
Promise.all(myPromises)
.then(resolve)
.catch(reject)
}
});
it may still fail if you made other mistakes.
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)
});
}