Chaining of .then does not work - javascript

I am using Firebase for a project and I am facing problems in chaining then().
I am storing the data about a user in an object. One of the attributes of user is an array of references to another set of data named events. I loop through the references to read the data in Firestore (Firebase DB) and store in to my local object called 'user'.
On printing the user object, the output statement of the third then() gets displayed first. Logically each then should be executed after the one above it but the third then() gets executed asynchronously and prints the output first. What is the reason for this? Also, no value is being returned by any of the then(). Is that the source of the problem?
orgRef.collection('collection').doc('doc').get()
.then(function(data){
user.info = data.data();
})
.then(function(){
user.info.events.forEach(function(event){
eventRef.get()
.then(function(data){
user.eventdata[event] = data.data()[event];
})
.then(function(){
console.log(user);
});
});
})
.then(function(){
console.log('AT END');
console.log(user);
});
I have added the output, each console.log statements is printing the same object 'user'. The object gets printed three times because the loop executes two times and prints the object. And the third is because of the then() statement to the main get() promise.
AT END
{
eventdata: {}
}
{
eventdata:
{ FEENbYcy04k6XPR148rv:
//more data
}
}
{
eventdata:
{ FEENbYcy04k6XPR148rv:
//more data
622kUqbF9jftq1nKkQSb:
//more data
}
}

You need to chain the promises properly instead. As is, your eventRef.get().thens are not connected to your final 'AT END' then.
Use Promise.all to turn an array of promises into a single one, and then return that promise for the third then.
orgRef.collection('collection').doc('doc').get()
.then(function(data) {
user.info = data.data();
})
.then(function() {
const allPromises = user.info.events.map(function(event) {
return eventRef.get()
.then(function(data) {
user.eventdata[event] = data.data()[event];
})
.then(function() {
console.log(user);
});
});
return Promise.all(allPromises);
})
.then(function() {
console.log('AT END');
console.log(user);
});
You could make it a lot more concise too by utilizing ES6 arrow functions and implicit return:
orgRef.collection('collection').doc('doc').get()
.then(data => user.info = data.data())
.then(() => (
Promise.all(user.info.events.map((event) => (
eventRef.get()
.then(data => user.eventdata[event] = data.data()[event])
.then(() => console.log(user))
)))
))
.then(() => {
console.log('AT END');
console.log(user);
});

No value is getting returned from your promises because of this:
.then(function(){
console.log(user);
});
This .then() handler has no return value which makes the resolved value of the promise become undefined. You need to return the value:
.then(function(data){
console.log(data);
return data;
});

Related

javascript Promise not wait Promise.all

So getAstronautsData make request to API then return array of promises. This promises mast make request to Wiki API and parse response in object. Then exampleAsyncFunc must wait all promises and return one big object with all info about Astronauts.
But if I use Promise.all function ending and console is clear.
function getAstronautsData() {
return new Promise((resolve, reject) => {
getData('http://api.open-notify.org/astros.json', "http", (data) => {
resolve(data) // get Astronauts list from API
})
}).then((astronautsList) => {
return astronautsList.people.map((person => // return array of promises
new Promise(resolve => {
getWikiData(person.name, (data) => { // request on Wiki API
resolve({info: data.extract, img: data.thumbnail.source})
})
})
))
})
}
async function exampleAsyncFunc (){
let promisesList = await getAstronautsData()
// next code just few variant was i try
let data = await Promise.all(promisesList)// it's not working.
console.log(data)
Promise.all(promisesList).then(data => console.log(data)) //it's not working. Function display nothing
promisesList.forEach((promise) => { //it's working but not so elegant
promise.then(data => console.log(data))
})
}
exampleAsyncFunc ()
function getWikiData(searhTerm, callback) {
getData(getUrlString(searhTerm), "https", (data) => {
const regex = new RegExp(searhTerm.replaceAll(" ", ".*"));
for (let page in data.query.pages) {
if (data.query.pages[page].title === searhTerm || regex.test(data.query.pages[page].title)) {
callback(data.query.pages[page])
return
}else{
callback(null)
}
}
})
}
You appear to be using Promise.all correctly, but if any of the Promises in Promise.all rejects, then overall Promise.all promise will reject too and nothing will happen, where in your forEach version it'll simply skip those promises silently and move on to the next entries.
Likewise if any of the promises in the list stays pending: if so then the Promise.all promise will never resolve. This could be because you have a long list of return values and the whole list takes a longer-than-expected time to resolve, or because your getWikiData call encounters an error and you don't pass that out to reject that particular promise in your array.
You can debug this behavior by ensuring that each of your calls to then is followed by .catch(console.error) (or some more robust error handler).
Let me first disclose that I am a big promise partisan and frankly deplore callbacks. The implication here is that I would not have written your getData and getWikiData with callbacks.
I will also point out that I second what #t.niese said in the comments: Because it does not make sense having both let data = await Promise.all(promisesList) and promisesList.forEach((promise) => {.
Anyway, your code is unnecessarily complex and can be simplified like so:
function getAstronautsData(callback) {
getData('http://api.open-notify.org/astros.json', "http", data => {
callback(data.people.map(person =>
new Promise(resolve => {
getWikiData(person.name, data => {
resolve(data);
})
}))
)
})
}
function exampleAsyncFunc (){
getAstronautsData(promises => {
Promise.all(promises)
.then(result => {
//result will contain those resolved promises
console.log(result);
})
});
}
exampleAsyncFunc ()
Notice that I am passing a callback to getAstronautsData and call it from inside that function with the array of promises you ultimately want to resolve. No need for async here either as you can see.
Ok, problem was in API (in API one of astronauts have name "Tom Marshburn" but on Wiki his page have title "Thomas Marshburn") and function getWikiData not return any data on error. So i fixed this problem.
Thanks you all for you help!!!

Handle a reject in promise.all() in javascript [duplicate]

I have an array of Promises that I'm resolving with Promise.all(arrayOfPromises);
I go on to continue the promise chain. Looks something like this
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
I want to add a catch statement to handle an individual promise in case it errors, but when I try, Promise.all returns the first error it finds (disregards the rest), and then I can't get the data from the rest of the promises in the array (that didn't error).
I've tried doing something like ..
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler()
.then(function(data) {
return data;
})
.catch(function(err) {
return err
});
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
But that doesn't resolve.
Thanks!
--
Edit:
What the answers below said were completely true, the code was breaking due to other reasons. In case anyone is interested, this is the solution I ended up with ...
Node Express Server Chain
serverSidePromiseChain
.then(function(AppRouter) {
var arrayOfPromises = state.routes.map(function(route) {
return route.async();
});
Promise.all(arrayOfPromises)
.catch(function(err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
// full array of resolved promises;
})
};
API Call (route.async call)
return async()
.then(function(result) {
// dispatch a success
return result;
})
.catch(function(err) {
// dispatch a failure and throw error
throw err;
});
Putting the .catch for Promise.all before the .then seems to have served the purpose of catching any errors from the original promises, but then returning the entire array to the next .then
Thanks!
Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.
Some libraries have something called Promise.when, which I understand would instead wait for all promises in the array to either resolve or reject, but I'm not familiar with it, and it's not in ES6.
Your code
I agree with others here that your fix should work. It should resolve with an array that may contain a mix of successful values and errors objects. It's unusual to pass error objects in the success-path but assuming your code is expecting them, I see no problem with it.
The only reason I can think of why it would "not resolve" is that it's failing in code you're not showing us and the reason you're not seeing any error message about this is because this promise chain is not terminated with a final catch (as far as what you're showing us anyway).
I've taken the liberty of factoring out the "existing chain" from your example and terminating the chain with a catch. This may not be right for you, but for people reading this, it's important to always either return or terminate chains, or potential errors, even coding errors, will get hidden (which is what I suspect happened here):
Promise.all(state.routes.map(function(route) {
return route.handler.promiseHandler().catch(function(err) {
return err;
});
}))
.then(function(arrayOfValuesOrErrors) {
// handling of my array containing values and/or errors.
})
.catch(function(err) {
console.log(err.message); // some coding error in handling happened
});
NEW ANSWER
const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));
FUTURE Promise API
Chrome 76: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
You can download https://www.npmjs.com/package/promise.allsettled to get it now. In certain browsers allSettled comes preinstalled with the browser itself. It's worth downloading the package for peace of mind because eg. TypeScript doesn't have default definitions for allSettled.
ES2020 introduces new method for the Promise type: Promise.allSettled().
Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.
async function() {
const promises = [
fetch('/api.stackexchange.com/2.2'), // succeeds
fetch('/this-will-fail') // fails
];
const result = await Promise.allSettled(promises);
console.log(result.map(promise => promise.status));
// ['fulfilled', 'rejected']
}
Read more in the v8 blog post.
To continue the Promise.all loop (even when a Promise rejects) I wrote a utility function which is called executeAllPromises. This utility function returns an object with results and errors.
The idea is that all Promises you pass to executeAllPromises will be wrapped into a new Promise which will always resolve. The new Promise resolves with an array which has 2 spots. The first spot holds the resolving value (if any) and the second spot keeps the error (if the wrapped Promise rejects).
As a final step the executeAllPromises accumulates all values of the wrapped promises and returns the final object with an array for results and an array for errors.
Here is the code:
function executeAllPromises(promises) {
// Wrap all Promises in a Promise that will always "resolve"
var resolvingPromises = promises.map(function(promise) {
return new Promise(function(resolve) {
var payload = new Array(2);
promise.then(function(result) {
payload[0] = result;
})
.catch(function(error) {
payload[1] = error;
})
.then(function() {
/*
* The wrapped Promise returns an array:
* The first position in the array holds the result (if any)
* The second position in the array holds the error (if any)
*/
resolve(payload);
});
});
});
var errors = [];
var results = [];
// Execute all wrapped Promises
return Promise.all(resolvingPromises)
.then(function(items) {
items.forEach(function(payload) {
if (payload[1]) {
errors.push(payload[1]);
} else {
results.push(payload[0]);
}
});
return {
errors: errors,
results: results
};
});
}
var myPromises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(new Error('3')),
Promise.resolve(4),
Promise.reject(new Error('5'))
];
executeAllPromises(myPromises).then(function(items) {
// Result
var errors = items.errors.map(function(error) {
return error.message
}).join(',');
var results = items.results.join(',');
console.log(`Executed all ${myPromises.length} Promises:`);
console.log(`— ${items.results.length} Promises were successful: ${results}`);
console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});
Promise.allSettled
Instead of Promise.all use Promise.allSettled which waits for all promises to settle, regardless of the result
let p1 = new Promise(resolve => resolve("result1"));
let p2 = new Promise( (resolve,reject) => reject('some troubles') );
let p3 = new Promise(resolve => resolve("result3"));
// It returns info about each promise status and value
Promise.allSettled([p1,p2,p3]).then(result=> console.log(result));
Polyfill
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises
.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
As #jib said,
Promise.all is all or nothing.
Though, you can control certain promises that are "allowed" to fail and we would like to proceed to .then.
For example.
Promise.all([
doMustAsyncTask1,
doMustAsyncTask2,
doOptionalAsyncTask
.catch(err => {
if( /* err non-critical */) {
return
}
// if critical then fail
throw err
})
])
.then(([ mustRes1, mustRes2, optionalRes ]) => {
// proceed to work with results
})
Using Async await -
here one async function func1 is returning a resolved value, and func2 is throwing a error and returning a null in this situation, we can handle it how we want and return accordingly.
const callingFunction = async () => {
const manyPromises = await Promise.all([func1(), func2()]);
console.log(manyPromises);
}
const func1 = async () => {
return 'func1'
}
const func2 = async () => {
try {
let x;
if (!x) throw "x value not present"
} catch(err) {
return null
}
}
callingFunction();
Output is - [ 'func1', null ]
if you get to use the q library https://github.com/kriskowal/q
it has q.allSettled() method that can solve this problem
you can handle every promise depending on its state either fullfiled or rejected
so
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
//do somthing
} else {
// do something else
}
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
For those using ES8 that stumble here, you can do something like the following, using async functions:
var arrayOfPromises = state.routes.map(async function(route){
try {
return await route.handler.promiseHandler();
} catch(e) {
// Do something to handle the error.
// Errored promises will return whatever you return here (undefined if you don't return anything).
}
});
var resolvedPromises = await Promise.all(arrayOfPromises);
Promise.allSettled with a filter
const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.
const resultFilter = (result, error) => result.filter(i => i.status === (!error ? 'fulfilled' : 'rejected')).map(i => (!error ? i.value : i.reason));
const result = await Promise.allSettled(promises);
const fulfilled = resultFilter(result); // all fulfilled results
const rejected = resultFilter(result, true); // all rejected results
Have you considered Promise.prototype.finally()?
It seems to be designed to do exactly what you want - execute a function once all the promises have settled (resolved/rejected), regardless of some of the promises being rejected.
From the MDN documentation:
The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.
The finally() method is very similar to calling .then(onFinally, onFinally) however there are couple of differences:
When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it.
A finally callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there's no need to provide it.
Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.
Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be fulfilled with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.
== Fallback ==
If your version of JavaScript doesn't support Promise.prototype.finally() you can use this workaround from Jake Archibald: Promise.all(promises.map(p => p.catch(() => undefined)));
We can handle the rejection at the individual promises level, so when we get the results in our result array, the array index which has been rejected will be undefined. We can handle that situation as needed, and use the remaining results.
Here I have rejected the first promise, so it comes as undefined, but we can use the result of the second promise, which is at index 1.
const manyPromises = Promise.all([func1(), func2()]).then(result => {
console.log(result[0]); // undefined
console.log(result[1]); // func2
});
function func1() {
return new Promise( (res, rej) => rej('func1')).catch(err => {
console.log('error handled', err);
});
}
function func2() {
return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
let sum = 0;
let promiseErrorArr = [];
Promise.allSettled(promises)
.then((results) => {
results.forEach(result => {
if (result.status === "rejected") {
sum += 1;
promiseErrorArr.push(result)
}
})
return ( (sum>0) ? promiseFailed() : promisePassed())
})
function promiseFailed(){
console.log('one or all failed!')
console.log(promiseErrorArr)
}
function promisePassed(){
console.log('all passed!')
}
// expected output:
// "one or all failed!"
// Array [Object { status: "rejected", reason: "foo" }]
Alternately, if you have a case where you don't particularly care about the values of the resolved promises when there is one failure but you still want them to have run, you could do something like this which will resolve with the promises as normal when they all succeed and reject with the failed promises when any of them fail:
function promiseNoReallyAll (promises) {
return new Promise(
async (resolve, reject) => {
const failedPromises = []
const successfulPromises = await Promise.all(
promises.map(
promise => promise.catch(error => {
failedPromises.push(error)
})
)
)
if (failedPromises.length) {
reject(failedPromises)
} else {
resolve(successfulPromises)
}
}
)
}
You can always wrap your promise returning functions in a way that they catches failure and returning instead an agreed value (e.g. error.message), so the exception won't roll all the way up to the Promise.all function and disable it.
async function resetCache(ip) {
try {
const response = await axios.get(`http://${ip}/resetcache`);
return response;
}catch (e) {
return {status: 'failure', reason: 'e.message'};
}
}
I've found a way (workaround) to do this without making it sync.
So as it was mentioned before Promise.all is all of none.
so... Use an enclosing promise to catch and force resolve.
let safePromises = originalPrmises.map((imageObject) => {
return new Promise((resolve) => {
// Do something error friendly
promise.then(_res => resolve(res)).catch(_err => resolve(err))
})
})
})
// safe
return Promise.all(safePromises)
You would need to know how to identify an error in your results. If you do not have a standard expected error, I suggest that you run a transformation on each error in the catch block that makes it identifiable in your results.
try {
let resArray = await Promise.all(
state.routes.map(route => route.handler.promiseHandler().catch(e => e))
);
// in catch(e => e) you can transform your error to a type or object
// that makes it easier for you to identify whats an error in resArray
// e.g. if you expect your err objects to have e.type, you can filter
// all errors in the array eg
// let errResponse = resArray.filter(d => d && d.type === '<expected type>')
// let notNullResponse = resArray.filter(d => d)
} catch (err) {
// code related errors
}
Not the best way to error log, but you can always set everything to an array for the promiseAll, and store the resulting results into new variables.
If you use graphQL you need to postprocess the response regardless and if it doesn't find the correct reference it'll crash the app, narrowing down where the problem is at
const results = await Promise.all([
this.props.client.query({
query: GET_SPECIAL_DATES,
}),
this.props.client.query({
query: GET_SPECIAL_DATE_TYPES,
}),
this.props.client.query({
query: GET_ORDER_DATES,
}),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;
Unfortunately, I don't have enough reputation to comment (or do much of anything, really), so I'm posting this as an answer in response to Eric's answer here.
The executor function can also be an async function. However, this is usually a mistake, for a few reasons:
If an async executor function throws an error, the error will be lost and won’t cause the newly-constructed Promise to reject. This could make it difficult to debug and handle some errors.
If a Promise executor function is using await, this is usually a sign that it is not actually necessary to use the new Promise constructor, or the scope of the new Promise constructor can be reduced.
From this explanation as to why Promises should not utilize an async executor function
Instead, you should opt for Promise.allSettled(), as suggested here by Asaf.
With the help of allSettled,we can now read the status of
each promise is, and process each error individually, without losing any of this critical information
const promises = [
fetch('/api/first'), // first
fetch('/api/second') // second
];
The simplest way is to handle errors
const [firstResult, secondResult] = await Promise.allSettled(promises)
// Process first
if (firstResult.status === 'rejected') {
const err = firstResult.reason
// Here you can handle error
} else {
const first = firstResult.value
}
// Process second
if (secondResult.status === 'rejected') {
const err = secondResult.reason
// Here you can handle error
} else {
const second = secondResult.value
}
A nice way to handle error
const results = await Promise.allSettled(promises);
const [first, second] = handleResults(results)
function handleResults(results) {
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)
if (errors.length) {
// Aggregate all errors into one
throw new AggregateError(errors)
}
return results.map(result => result.value)
}
That's how Promise.all is designed to work. If a single promise reject()'s, the entire method immediately fails.
There are use cases where one might want to have the Promise.all allowing for promises to fail. To make this happen, simply don't use any reject() statements in your promise. However, to ensure your app/script does not freeze in case any single underlying promise never gets a response, you need to put a timeout on it.
function getThing(uid,branch){
return new Promise(function (resolve, reject) {
xhr.get().then(function(res) {
if (res) {
resolve(res);
}
else {
resolve(null);
}
setTimeout(function(){reject('timeout')},10000)
}).catch(function(error) {
resolve(null);
});
});
}
I wrote a npm library to deal with this problem more beautiful.
https://github.com/wenshin/promiseallend
Install
npm i --save promiseallend
2017-02-25 new api, it's not break promise principles
const promiseAllEnd = require('promiseallend');
const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};
// input promises with array
promiseAllEnd(promises, {
unhandledRejection(error, index) {
// error is the original error which is 'error'.
// index is the index of array, it's a number.
console.log(error, index);
}
})
// will call, data is `[1, undefined, 2]`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// input promises with object
promiseAllEnd(promisesObj, {
unhandledRejection(error, prop) {
// error is the original error.
// key is the property of object.
console.log(error, prop);
}
})
// will call, data is `{k1: 1, k3: 2}`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
// won't call
.then(data => console.log(data))
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
// will call, data is `[1, undefined, 2]`.
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
————————————————————————————————
Old bad api, do not use it!
let promiseAllEnd = require('promiseallend');
// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
.then(data => console.log(data)) // [1, undefined, 2]
.catch(error => console.log(error.errorsByKey)) // {1: 'error'}
// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
.then(data => console.log(data)) // {k1: 1, k3: 2}
.catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

Nesting .then() functions

Is it bad practice to nest multiple then functions? It seems fairly logical to say "execute this function, and when it's done, execute this one" (and so on) but the code looks horrible.
If it helps I originally had this query in the context of firestore getting user details then getting documents
firebaseApp.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
//If error
}).then(()=>{
firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get().then((snapshot)=>{
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
})
}).then(()=>{
//Tell the user in the UI
});
});
Are there alternatives? One that springs to mind is like so
var functionOne = () =>{
console.log("I get called later");
}
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 3000);
});
promise1.then(function(value) {
functionOne();
});
But even then it seems like it could get complex after a few .then()'s
Return the Promise from the first outer .then, and then use the resolve value in a second outer .then, without any nested .thens:
firebaseApp.auth().signInWithEmailAndPassword(email, password)
.then(()=>{
return firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
})
.then((snapshot) => {
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
})
.catch((error) => {
// handle errors
});
Make sure not to catch too early - if there's an error anywhere in the chain, often you'll want to stop normal execution and go directly to the end (eg, tell the user that there was an error).
If you're worried about code readability, consider using async/await (and transpile down your production code for older browsers):
// in an async function:
try {
await firebaseApp.auth().signInWithEmailAndPassword(email, password);
const snapshot = await firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
} catch(error) {
// handle errors
}
It depends on what you want to do: If you need access both to the result passed into then and to the result of a subsequent operation you're doing within the then at the same time, nesting is reasonable:
doSomething()
.then(result1 => {
return doSomethingElse()
.then(result2 => {
return result1 + result2;
});
})
.then(combinedResult => {
// Use `combinedResult`...
})
.catch(/*...*/);
often, though, you just need to pass a single value through the chain, by returning the promise from your subsequent operation from the then handler:
doSomething()
.then(result => {
return doSomethingElse(result);
})
.then(lastResult => {
// `lastResult` is the fulfillment value from `doSomethingElse(result)`
})
.catch(/*...*/);
Doing that resolves the promise then created to the promise returned by get() on the query. (To "resolve a promise to something" means that you've made the promise's settlement depend on the thing you've resolved it to. If you resolve it to another promise, its settlement depends on the settlement of that other promise.)
Looking at your Firebase example, I'd probably do it without nesting:
firebaseApp.auth()
.signInWithEmailAndPassword(email, password)
.then(() => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get())
.then((snapshot) => {
snapshot.docs.forEach(doc => {
// Do stuff with data
});
})
.then(() => {
// Tell the user in the UI
})
.catch(function(error) {
// Handle/report error, which may be from `signInWithEmailAndPassword`, your collection query, or an error raised by your code in the `then` handlers above
});
You should chain promises and, also, you can name the functions, which IMHO can improve readibility significantly. Consider something like this
const signIn = () => firebaseApp.auth().signInWithEmailAndPassword(email, password);
const onSigninError = (err) => // error handling logic here
const getCollection = () => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID)
.get();
const processSnapshot = (snapshot) => snapshot.doc.forEach(// do stuff here
const displayMessage = () => // do stuff here
signIn()
.catch(onSigninError)
.then(getCollection)
.then(processSnapshot)
.then(displayMessage);

Nesting a http request inside list of promises

I have a service function that returns a list of promises:
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
, which I'm calling inside my controller:
validateUser() {
this.error = null;
this.service.getData(this.$scope.username)
.then(resp => {
if (resp.userInfo.data.user) {
this.service.storeUserData('userData', JSON.stringify(resp));
this.$location.path('/profile');
} else {
this.error = resp.userInfo.data.message;
}
})
}
Works fine until now but what I'm looking for is manipulating what data I get from the userChart request in particular.
I want to manipulate the json I get from calling userChart, store some of it inside of an array and make another request that returns data using the stored array object values from the initial request as parameters.
So basically I don't need the json from userChart, I just need to use to it make a nested(?) request using some of its data.
If you return a promise from the then the caller of the original promise will wait until the nested promise is resolved. It does not matter if the caller was your service using $q.all or something else, it is chained.
This shows just the relevant code, it located in your service with everything else being unchanged.
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
.then((result) => {
// additional manipulation if needed on result
// second call to http and return the resulting promise
return this.$http.doSomething('');
});
I haven't tried this, but maybe you could do something with the result of that call as soon as it comes back? Something like this?
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user)).
then(function(response) {
// Do something with the successful response
}, function(response) {
// Do something for the failed response
})
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
If I understood yours needs you should first get the charts response and then use that response to call the other webservices, so something like this should work:
validateUser() {
this.getData({})
.then(function (response) {
// response containing 'userInfo' and 'userTopArtists'
console.log(arguments);
});
}
getData(user) {
const me = this;
return me.$http.get('https://jsonplaceholder.typicode.com/users')
.then(function (charts) {
return me.$q.all({
// use charts as param for other calls...
userInfo: me.$http.get('https://jsonplaceholder.typicode.com/users'),
userTopArtists: me.$http.get('https://jsonplaceholder.typicode.com/users')
});
})
.catch(err => {
me.$q.reject('Error' + err.status);
});
}

How do I use Promise.all() to defer returning an array that's being built inside the function?

Below I'm attempting to assign a value, an array, to a local variable. I'm calling a function that returns an array to get that value. The problem is that the Promises in the 2nd function aren't resolved until after the array has been returned to the caller. I've tried using Promise.all() on retArray but it never works for me. When I console.log() out my someobject object the arrOfTitles field never prints out because the call to SkywalkerTitles() returns an empty array.
You can run the code here.
So how do I get someObject.arrOfTitles to get the array of titles from SkywalkerTitles()?
function SkywalkerTitles(){
let retArray = [];
fetch('https://swapi.co/api/people/')
.then(function(response){
response.json()
.then(function(result){
return result.results[0];
})
.then(function(result){
result.films.forEach(function(film){
fetch(film)
.then(function(response){
response.json().then(function(result){
console.log(result.title);
retArray.push(result.title);
});
});
})
.catch(function(error){
console.log(error)
});
});
})
.catch(function(error){
console.log(error)
});
}
function UseReturnedArray() {
let someObject = { aThing: '', anotherThing: '', arrOfTitles: null };
someObject.aThing = 'Thing One';
someObject.anotherThing = 'Thing Two';
someObject.arrOfTitles = SkywalkerTitles();
console.log('Object With Returned Array:\n\n', JSON.stringify(someObject, null, 2));
}
UseReturnedArray();
As much as #Svenskunganka answer is complete and worthy, alternative if your environment doesn't yet support async/await AND you don't want to use a transpiler - seeing as you're already partially familiar with Promises, this code shouldn't look as foreign :p
Your main problem is that SkywalkerTitles doesn't actually return anything (you claim it returns an empty array, it actually has no return statement, therefore the returned value is undefined
I've also removed the .catch code, because where you had it would actually cause issues, in that your code handles rejections, yet further down the chain the code would expect that the data was actually valid! Only a single catch almost always ever needed
function SkywalkerTitles() {
return fetch('https://swapi.co/api/people/').then(function (response) {
return response.json();
}).then(function (result) {
return result.results[0];
}).then(function (result) {
return Promise.all(result.films.map(function (film) {
return fetch(film).then(function (response) {
return response.json();
}).then(function (result) {
return result.title;
});
}));
});
}
function UseReturnedArray() {
SkywalkerTitles().then(function (arrOfTitles) {
var someObject = {
aThing: '',
anotherThing: '',
arrOfTitles: arrOfTitles
};
console.log('Object With Returned Array:\n\n', JSON.stringify(someObject, null, 2));
}).catch(function(reason) {
console.log(reason);
});
}
UseReturnedArray();
Note how the promise chain is flattened compared to yours, and use of Array#map instead of Array#forEach + Array#push
You should take advantage of async/await, which could really clean up your code quite a bit and make it more understandable:
async function SkywalkerTitles () {
let character = await fetch('https://swapi.co/api/people/').then(res => res.json()).then(res => res.results[0])
return await Promise.all(character.films.map(async (film) => {
return await fetch(film).then(res => res.json()).then(res => res.title)
}))
}
async function UseRetrunedArray () {
try {
let someObject = {
aThing: '',
anotherThing: '',
arrOfTitles: await SkywalkerTitles()
}
console.log(someObject)
} catch (e) {
console.error(e)
}
}
UseRetrunedArray()
See https://repl.it/Lc2f/2
If this looks alien to you, I suggest you read a bit into how async/await and Promise work together.

Categories

Resources