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

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

Related

Json fetch returning elements in random order

I am having trouble with a fetch request, the thing is that for some reason it's returning the elements in random order. The function is the following:
function create_post_div(element, isLogged, currentUser, isProfile) {
console.log(element.id);
fetch(`likes/${element.id}`)
.then((response) => response.json())
.then((like_element) => {
console.log(like_element.user_liked, element.id);
});
}
For simplicity's sake I didn't put the whole function but the error can be detected with the console log. The first console log should return the element.id which are integers and the second the like_element.user_liked which is a boolean and the same element.id.
The create_post_div function has to run for each of the elements received from another function (let's say a list [1, 2, 3]). When I run the function the first console.log returns the numbers in the correct order, but the console.log inside the fetch return them randomly. Other thing that I noted is that instead of the console be something like:
1
true, 1
2
false, 2
3
true, 3
It logs like this:
1
2
3
false, 2
true, 3
true, 1
It will first log all of the id's and after log the likes with the ids. Again, the id's in the first log always in order and the second log always unordered.
When you use fetch you create a Promise, an operation that will take an undefined amount of time to complete. And Javascript doesn't wait for the promise to be completed, it keeps running doing all the other stuff that you have coded.
Once the fetch operation is completed the code that you put inside the .then is executed, that is why it's called asynchronous code, because it's exectuted after an undefined amount of time.
Rather than fighting this and trying to force the code to run syncronously with async / await like suggested I would reccomend rewiring your brain to understand and embrace this pattern. Unless it's absolutely necessary by design but it should be a very rare situation.
You're just dealing with the beautiful of asynchronous operations.
See the last example on the TutorialsPoint NodeJS Course. This code:
const fs = require("fs");
fs.readFile("input.txt",
(err, data) => err ? console.log(err.stack) : console.log(data.toString())
);
console.log("Program Ended");
outputs:
Program Ended
Some text.
In your case, you:
Call create_post_div
Logs the id
Starts fetching
Call create_post_div again
Logs the id again
Starts fetching again
Call create_post_div again
Logs the id again
Starts fetching again
Then, for each fetched data (which is a random operation, as you can see...), logs it as the data is received.
But, if there's no data received, your code doesn't log it. It just does so as when there is data.
When you call fetch().then, the then method comes from a Promise. And JavaScript Promises promises to return / do something in the future, but not right now.
If you want more explanation, I will leave you with a link to a similar question in the comments.
The fetch is asynchronous so console.log in the callback .then() could be in any order as you observed.
Most of the time this is fine. It should be another part of your code should be changed to adopt this async pattern.
However, if you want to force it running in a synchronous order, you can run it with a reduce.
Here is an example
const elements = [1, 2, 3];
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// this prints 3,2,1
await Promise.all(
elements.map(async (i) => {
await sleep(10 - i);
console.log(i);
})
);
// and this prints 1,2,3
await elements.reduce(async (prev, i) => {
await prev;
await sleep(10 - i);
console.log(i);
}, undefined);

Calling promise while mapping over array

I'm experiencing some weird behavior when using promises inside of a map.
This isn't really a problem per se, but I'd like to understand what's going on.
let books = [
{Name: "Moby Dick",
AuthorId: 1
},
{Name: "The Great Gatsby",
AuthorId: 2}
]
let authors = [
{AuthorId: 1,
Name: "Herman Melville"
},
{AuthorId: 2,
Name: "F. Scott Fitzgerald"
}
]
const getAuthorName = (AuthorId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(authors.find((author) => {
return author.AuthorId === AuthorId
}).Name)
}, 1000
})
}
let responseArray = []
let promises = books.map((book) => (
getAuthorName(book.AuthorId).then((res) => {
responseArray.push({
...book,
AuthorName: res
})
})
))
setTimeout(() => console.log(responseArray), 500)
//I would expect to have to do this:
//Promise.all(promises).then((res) => console.log(res))
I would expect the
setTimeout(() => console.log(responseArray), 5000)
to log an empty string, because the array of promises hasn't been run through Promise.all yet, but it seems like even though the map should just be returning an array of promises, it's actually running the promises. Why is this?
Edit
I've edited the getAuthor promise to wait a second before resolving to further elaborate because that wasn't the point I was trying to get at.
I would expect mapping over an array, to return a new array with whatever is returned in the map. For example, if I do
let arrayOfPromises = books.map((book) => {
return getAuthor(book.AuthorId)
}
I would expect arrayOfPromises to be an array of getAuthor promises.
However, when I throw a .then() on the end of the promise that is being returned, it appears that the code in the .then() is evaluating.
So if I do
let promises = books.map((book) => {
return getAuthor(book.AuthorId).then((res) => {
console.log(res)
})
}
In the console I'll see "Herman Merville" and "F. Scott Fitzgerald" and in the promises var I'll have an array of promises.
While I thought that each getAuthor's .then would only evaluate once I call Promise.all(promises) because the getAutor promise being returned inside the map. Am I understanding something wrong here?
That's because the code inside your promise isn't asynchronous, so it's resolving instantly as there's nothing to wait for.
To expand a bit on this, what you are doing is the same as
const getAuthorName = (AuthorId) => {
return Promise.resolve(authors.find((author) => {
return author.AuthorId === AuthorId
}).Name)
}
It will allow you to chain a then syntax on the function, but that also mean that the then will be executed directly after the function call.
edit
Following your edit, here's a bit more.
You've modified the method to be asynchronous using the setTimeout. Which means that the map is now return an array of promises as you expected.
However there's another issue with your code, you have also encapsulated the console.log in a setTimeout, one with an even greater timer!
To better understand what is going on here, I highly recommend that you watch this video.
However in the spirit of StackOverflow, I'll add some written explanations here.
You are first creating an array of promises, executing callbacks after approximately 1s after their creations (they won't wait for the map to be over).
As soon as they are done, they go in what's called the event loop of your browser. Basically it's a pile of events that your browser will handle as soon as the main thread is freed.
So 1s after their creations the promises will resolve, and place their callbacks on this event loop.
Now what happens when you run your console.log.
Well if you didn't put it in its own setTimeout, it would print an empty array.
What if it was a setTimeout of 500ms? Here we can safely assume that the map would take less than 500ms to finish and start the new setTimeout so the console.log would reach the event loop first and print an empty array once again.
What about 5s? Both setTimeouts will be over long before the console.log is reached, so you will print an filled array.
I made a jsfiddle if you want to try it out for yourself.
I hope this helps clear things up.

Rxjs bind context of place where observable was created

I have an array of observables which was created in a loop. And then merged all these observables using merge, and subscribed to the merged observable. I could not find a way to retrieve context of observable where it was created (in loop). Here is code
let observable = Rx.Observable.bindNodeCallback(request);
let streams = _(['a', 'b', 'c', 'd'])
.someMoreLodashStuff()
.map(val => {
// HERE SOMEHOW I WANT TO BIND CONTEXT (e.g. loop val),
// SO THAT SUBSCRIBER CAN KNOW THE EXACT LOOP STATE
// WHEN RECEIVING RESULT
return observable(mutate(val))
})
.value();
Rx.Observable
.merge(...streams)
.subscribe(
(res) => {
// HERE I WANT TO GET CONTEXT (e.g. val)
}, (err) => {
// HERE I WANT TO GET CONTEXT (e.g. val)
},
() => {
//on complete stuff
});
Update (as asked by #martin)
Since #martin asked about purpose of this binding and what problem I am trying to solve, so I will describe the real problem.
Pupose and real problem
I am trying to crawl list of websites (passed as query params), fetch their titles and render them in an html and return the html back to user. This is part of my open source repo, where solved this exact problem using node.js callbacks, async.js waterfall and promises. Now solving it using rxjs. Its just a way to learn different async techniques. This is file from github repo where using rxjs to solve this problem
If you want to retain a reference to the input state you could use the flatMap overload which takes a resultSelector function to create a tuple containing the merged input + output state:
// given a function which can return the body of the page requested
function doRequest(url) : Observable<string>
const urls = Rx.Observable.from([a,b,c])
.flatMap(
a => doRequest(url),
(a,res) => ({ url: a, body: res})
)
.subscribe(resTuple => console.log(`url ${resTuple.url} returned ${resTuple.body}`)

for loop and promises

I have three methods (each is a promise) that does SELECT from the database.
getDeliverDate()
.then(calculateOrderList)
.then(getOrderItemsForOrder)
For each deliverDate, I need to get the corresponding list of orders, and for each order I need to get the list of items. I will then build a JSONview to send to the client. To do that, I think I need to wrap the code above with a for loop, and presumably have a "global" object made of arrays etc.. and have to push to the object data from each method. I am not really sure if the approach will work. Can someone please tell me how to get it to work using the code above as an example.
Push all the promises into an array then use Promise.all to wait for all promises to finish and return the result. Something like
var promises = []
for (...) {
promises.push(getDeliverDate()
.then(calculateOrderList)
.then(getOrderItemsForOrder));
}
return Promise.all(promises);
Edit, all not when -- got my promise libraries confused
Better way to do that is use Promise.all:
Promise.all([getDeliverDate, calculateOrderList, getOrderItemsForOrder])
.then(values => {
console.log(values);
});
If what I understand is correct (as given in comments):
getDeliverDate()
// get array of delivery dates
.then((deliveryDates = []) => {
// for each delivery date, I need to get corresponding list of orders
const promises = deliveryDates.map(date => calculateOrderList(date));
return Promise.all(promises);
})
// get order list as an array
.then((orderList = []) => {
// for each order I need to get list of items corresponding to that order
const promises = orderList.map(order => getOrderItemsForOrder(order));
return Promise.all(promises);
})
// get item list as an array
.then((items) => console.log('got everything : ', items))
.catch((error) => console.log('error : ', error))
Looks like you need a lot of API calls. Probably we should think about optimizing backend.

Nested promises and how to get around em

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

Categories