Nested promises and how to get around em - javascript

I am using a promise to get some JSON from a URL. The JSON that is returned includes a list of new URLs that return JSON. My current implementation is failing due to the nested promises.
I need to do the following:
request parent JSON url
request each of the child JSON urls
After each child promise returns JSON, I need to do some stuff with the child's JSON and the parent JSON.
I am getting the following error.
Warning: a promise was created in a handler at main.development.js:661:61 but was not returned from it
Boiled down version of my code:
myPromise(url)
.then(response => {
// process the data into an array of items
items.forEach(item => {
myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
});
});

Here I've done your example, using Bluebird map.
I've also added the concurrency option, this is very handy.. Leaving out, will just work a bit like promise.all, and putting a value of 1, would be were you want to do all the promises in series..
myPromise(url)
.then(response => {
// process the data into an array of items
return Promise.map(items, item => {
return myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
}, {concurrency:10}); //lets do a max of 10 promises at a time.
});

You error is actually just a warning. It is there for good reason; a common mistake is doing something like this
myPromise(url)
.then(response => {
somethingElseAsync(response);
})
.then(myCallback);
and expecting myCallback to be invoked after somethingElseAsync has finished work. As far as I can tell, this is not your case, since you are not collecting the results of your child promises.
To suppress the warning, you can follow Keith's answer. As a bonus, you can tack another promise onto your chain which will resolve when all child promises have resolved.
As an alternative to Promise.map, if you are okay with spawning all child tasks simultaneously, you can get away with Promise.all, like this:
myPromise(url).then(response => {
return Promise.all(items.map(item => {
return myPromise(item.url).then(response2 => {
// handle response and response2, return some result
return result;
});
}));
}).then(results => {
// ^^^ an array of results returned from child promise callbacks
}).catch(error => {
// either the parent promise or one of the child promises has rejected
});

Related

How to wait for multiple nested promises to resolve?

I am attempting to make several http requests (using axios), one to each url in a list. As soon as an individual request resolves, I want to make another request to a url constructed from the previous request's response. Finally, when all secondary requests are resolved, I want to do something with all of their responses.
I have attempted to accomplish this as follows:
let promises = [];
urls.forEach(url => {
const promise = axios.get(url);
promise.then(response => {
promises.push(axios.get(response.data.url))
});
});
Promise.all(promises).then(responses => {
// do something with responses
})
I expect the responses of all secondary url requests to be resolved when .then(responses => {}) triggers, yet when this code triggers the responses array is empty.
How can I achieve something like this?
Return the nested axios.get to a mapping callback, so that Promise.all will wait for all nested requests to complete first.
Promise.all(
urls.map(
url => axios.get(url).then(
response => axios.get(response.data.url)
)
)
)
.then((results) => {
// ...
})
This is logical considering that very first promise.then() statement is asynchronous. The urls.forEach althoughy seemingly asynchronous, it is synchronous. Thus the Promise.all() is called before any promises are pushed to your array.
You could set it up like so:
let promises = [];
urls.forEach(url => {
promises.push(axios.get(url).then((response) => {
return axios.get(response.data.url);
});
});
Promise.all(promises).then(responses => {
// do something with responses
});
This chains the .then() directly onto the first axios request, which is then pushed onto the promises array.

Passing parameters to a promise

I am trying to write a promise in such a way that I can pass some parameters. Inside, this promise will call a Fetch. This is the code I wrote so far:
const myFunction = (parA, parB, parC) => {
return new Promise ((resolve, reject) => {
url = ... // calculated based on the parameters passed above;
fetch(url)
.then(response => {
var object = response.json();
resolve(object);// the idea is that 'object' is returned by the outmost promise
console.log('Check');// So far OK, this is correctly printed to the console
})
.catch(err => {
console.log('Error: ' + err);
reject(err);
});
// resolve('1') // Used for test only. With this, everything works, but "object" here is undefined -- I guess it gets executed too early, before the completion of the Fetch
});
and this is where the promise is called
myFunction(a, b, c).then(res => {
console.log('OK');// just testing
console.log(res);// just testing
});
What happens is that the Fetch resolves OK, but my overall promise doesn't. The last two console.log instructions are never executed.
In short, my problem is: how can I resolve my promise returning the result from the Fetch? It's a promise inside a promise, I guess I could chain them with .then, but I also want to be able to pass parameters.
I also guess I could rewrite the code avoiding this promise chaining, but since I'm also doing it as a way of learning, I prefer to try to figure out first how to solve this problem with this structure.
p.s.: at the moment I am stuck with the promises, cannot use async/await because I am with an old version of node.js and cannot update it
As #VLAZ already mentioned, it's not necessary to create your own promise here, since fetch() itself already returns a promise.
Therefore, you could try this:
const myFunction = (parA, parB, parC) => {
const url = ... // calculated based on the parameters passed above;
return fetch(url)
.then(response => response.json())
.then(object => {
console.log('Check', object);
return object;
})
.catch(err => {
console.log('Error: ' + err);
});
};

Receiving "push is not a function" on JS reducer result array

I have the following function that retrieves an array of playlists then attempts to build a simplified version in a new array using a reducer on the fetched array.
The first iteration shows that paredPlaylists contains a single element that looks like I expect. On the second, it instead becomes a Promise and then gives the error
paredPlaylists.push is not a function
I understand that the error itself is explained by the resulting array (paredPlaylists) no longer being an array, but a promise. But how does it successfully push the simple object {id: "foo"} onto the array in the first pass then get converted to a promise in the second?
async function getPlaylists(token) {
return await fetch('https://somewhere')
.then((response) => response.json())
.then(async (response) => {
return await response.items.reduce(async (paredPlaylists, playlist) => {
await paredPlaylists.push({
"id": "foo"
})
}, [])
})
}
Note: I'm still pretty new to React and JS and have only partially gotten a grasp on promises and forcing async. I expect I have a number of awaits that are unnecessary. Early attempts did not have so many, I just kept peppering them to hopefully get actual objects to work with vs. promises.
the reduce fn takes an array and run a callback for each item.
If you're working with an array of array consider using the concat
return response.items.reduce( (a, b) => a.concat(b), [])
If it's just an array, then just map your items as u prefer
return response.items.map(playlist => ({ id: playlist.id }) )

Synchronous loop in Promise all

I would like to do a synchronous loop in a part of my code.
The function saveInDatabase checks if the item title (string) already exists in the database. That's why it can't be resolved in parallel, otherwise the condition will never apply (and would create duplicates).
Promise.all(arr.map(item => {
saveInDatabase(item).then((myResult) => ... );
}));
I tried to encapsulate this function into separate promises, also tried with npm packages (synchronous.js, sync), but it seems that it does not fit with my code.
Maybe this solution is completely silly.
Do you think it's a better idea to replace promise.all by a synchronous loop (foreach for example) ? The problem is that I need the results of each iteration...
I'm using Node 6.11.2. Could you give me some tips to handle that ?
Thank you in advance.
Without using await (which is not in node.js v6.11.2, but would make this simpler), a classic pattern for serializing a bunch of async operations that return a promise is to use a reduce() loop like this:
arr.reduce(function(p, item) {
return p.then(function() {
return saveInDatabase(item).then((myResult) => ... );
});
}, Promise.resolve()).then(function() {
// all done here
}).catch(function(err) {
// error here
});
If you want to save all the results, you can use your .then(myResult => ...) handler to .push() the result into an array which you can access when done.
This will serialize all the calls to saveInDatabase(item) to it waits for the first one to be done before calling the second one, waits for the second one to be done before calling the third one, etc...
The default implementation here will stop if saveInDatabase(item) rejects. If you want to keep going (you don't say in your question), even when it gives an error, then you can add a .catch() to it to turn the rejected promise into a fulfilled promise.
In node.js v7+, you can use await in a regular for loop:
async function myFunc() {
let results = [];
for (let item of arr) {
let r = await saveInDatabase(item).then((myResult) => ... );
results.push(r);
}
return results;
}
myFunc().then(function(results) {
// all done here
}).catch(function(err) {
// error here
});
If you could run all the requests in parallel, then you could do that like this:
Promise.all(arr.map(item => {
return saveInDatabase(item).then((myResult) => ... );
})).then(function(results) {
// all done here
}).catch(function(err) {
// error here
});
In any of these, if you don't want it to stop upon a rejection, then add a .catch() to your saveInDatabase() promise chain to turn the rejection into a resolved promise with some known value or error value you can detect.

Get the value of a Promise?

I'm using axios to make an AJAX request to the twitchtvapi. I assigned promise to a variable called example.
Is was it is possible to get the data inside of the Promise object? If I chain the .then method to the promise object I get an error that .then is not a function.
If I log the example variable I can see that there are three promises with the values that I'm looking to store into an array similar to
var example = users.map((item) => axios.get(`https://api.twitch.tv/kraken/streams/${item}?client_id=${client_id}`)
.then(res => {
console.log(res.data.stream);
return res.data.stream;
})
.catch(error => {
console.log(error);
}));
console.log(example); //an array of three Promise objects
console.log(example.then(res => res.data)) //returns error example.then is not a function
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
TLDR; How can I get the data from a Promise object? If I try to chain the .then method I get an error that says .then is not a function.
Thank you for any help in advance.
The following code
users.map((item) => axios.get(`https://api.twitch.tv/kraken/streams/${item}?client_id=${client_id}`)
returns an array of promises. An array by itself is not a promise. If you wish to execute some code after all the promises have been resolved, you can use Promise.all
Promise.all(users.map(...))
.then((responses) => {
// deal with responses[0].data
// responses[1].data
// etc.
})
.catch((error) => {
// deal with error that occurred
});

Categories