should set state in callback after sorting function? - javascript

in my componentDidMount method I partition the data into two separate lists and then set state of the two lists so my UI can render them. I have two questions:
is the componentDidMount() method the appropriate place to partition a list received from an API call?
is it possible for the code to attempt to set the state before lodash has completed the partitioning (lets say its a really long list)? if so, I tried a .then() callback but I am getting an error saying .then() is not a function.
componentDidMount() {
const { data } = this.props;
let currentTime = moment();
let sortedPosts = _.partition(this.state.listViewData, function(o) {
return (o.isActive || (new Date(o.scheduledActiveEnd)) < currentTime);
}).then(() => {
this.setState({
futureListViewData: sortedPosts[0],
currentListViewData: sortedPosts[1]
})
})

is the componentDidMount() method the appropriate place to partition a list received from an API call?
Yes, componentDidMount is a good spot for putting asynchronous code, such as fetching data and then parsing the response.
is it possible for the code to attempt to set the state before lodash has completed the partitioning (lets say its a really long list)? if so, I tried a .then() callback but I am getting an error saying .then() is not a function.
No, _.partition is synchronous. It returns an array of arrays, not a promise. If you call _.partition on a large array, the thread will block until its done partioning the array.

Related

Do i need to make this function asynchronous to wait for the filter method?

I have the state array "days" and I have a function that gets the element with an id given by the argumet, i am using the array.filter method to do this. Assuming the array has a lot of data in it, do i need to make the function asynchronous to wait for the filter method to print that data?
const [days, setDays] = useState([]);
const onDayClicked = async (id) => {
const daySelected = await days.filter(el => el._id === id)
console.log(daySelected)
}
filter returns:
A new array with the elements that pass the test. If no elements pass the test, an empty array will be returned.
An array is not a promise.
await is only useful if you have a promise on the RHS.
If you have an expensive, blocking operation to do, then it is simply an expensive operation that will block the event loop. You can't get around that by throwing promise syntax at it.
You could get around it by moving the work out of the main event loop and into a web worker.
If you did that, then you could create a promise that resolves when the web worker sends the results back to the main event loop.
And if you did that, then you could use await so that daySelected would have the resolved value in it.

pushing api responses to global variable inside .map() somehow makes data inaccessible

I created a function that makes a series of API calls through a Promise like this
//userApiList is a variable with an array of links
return Promise.all(userApiList.map(url =>{
var a = $.getJSON(url);
//userData is a global variable
userData.push(a);
return a;
}));
I want to both save the data to a variable for later use and return it right away to be iterated into html with jquery. Everything loads up perfectly as expected, however when I go to access the data in the variable I get all "undefined" properties. I console.logged the variable and it shows the data is there, I click the buttons well after the data is fetched so it's not an async issue. here's an example of one of the functions
$("#load_online").click(function(){
var users = [];
for(var i = 0; i < userData.length; i++){
var status = userData[i].status;
if(status !== null || status !== undefined){ users.push(userData[i]); }
}
$("#result_frame").empty();
//loadUsers() iterates the data into html
loadUsers(users);
});
I tried console.log(userData[i].status) just to see what the results would be and I got 200 as a response when it's suppose to be null' or the title of an episode being streamed on the channel.
The problem is I thought the responseJSON: field is what's always returned for use, as I've never walked into this issue before. This time it seems the whole object is being read therefore userData[i].status was reading the status: property a layer up rather than inside the responseJSON: object. I tried thinkering with getting a response out of userData[i].responseJSON.status and that returned undefined for each object. Does anybody readily see what I'm doing wrong? here's a CodePen of the overall project if you need a closer look at things.
You're not pushing the data into your array, you're pushing a jQuery jqXHR object (which is thenable, e.g., Promise-like) into it (that's what the return value of $.getJSON is). The reason the Promise.all result works is that Promise.all waits for those thenables to resolve.
It's quite likely you don't want to have a userData global at all. Instead, have whatever code needs the data get it from a resolution handler on the the promise returned by Promise.all.
But if you really want to populate userData, wait for resolution:
return Promise.all(userApiList.map($.getJSON))
.then(results => {
userData.push(...results);
return results;
});
Note that userData won't have the data until the ajax requests complete (of course).
The above won't add the results to userData until they're all avaialble. You also have the option of populating it as you go:
return Promise.all(userApiList.map(url => $.getJSON(url).then(result => {
userData.push(result);
return result;
})));
But it's important to remember the results will be added over time, not instantaneously, as the requests complete.
If you wait for all of them, Promise.all will ensure they're in the same order as the promises you giave it; if you add them as you go, they may not be in that order (because you're pushing as each one completes, and an earlier one may complete after a later one).
agreed with the upper answer,
this may work too,
return userApiList.map(url => { $.getJSON(url).done(function(results){
userData.push(results)
});})

Javascript - access variable in then outside it's scope [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 4 years ago.
I have the following Javascript and I am trying to access the content data out of the service scope, what's the best way to access that data?
Service
.getContent()
.then((content = {}) => {//access this content out of 'Service' scope})
.catch((error = {}) => {console.log('errror', error)})
I tried the following:
let data = null;
Service
.getContent()
.then((content = {}) => {data = content})
.catch((error = {}) => {console.log('errror', error)})
console.log(data);
But I get the error that data is undefined. How can I get the contents of content to data
You can't do it this way. The issue is that a .then() handler is called at some indeterminate time in the future. It's always called at least on the next tick of the event loop (per the promise specification) and if there's a real asynchronous operation behind the promise, then who knows when it will be called (it could be ms to hours to never).
The ONLY way you can possibly know when it gets called and thus when a value provided in the .then() handler is available is by using the data it provides inside the .then() handler itself or by calling some function from within the .then() handler and passing the data to it.
Your console.log(data) statement always runs BEFORE the .then() handler is called. That's why you can't use the variable there. And, the only place you actually know when the data is available is inside the .then() handler so that's where you need to consume the data.
This is a different way of thinking about coding. It's the asynchronous model that Javascript uses for lots of things and you do need to learn it in order to be successful with Javascript.
So, the proper way to do things is this:
Service.getContent().then((content = {}) => {
// use content here
console.log(content);
}).catch(err => {
// handle error here
console.log('errror', err)
});
FYI, ES7 allows you to use await to make your code "look" a little more synchronous. It's important to understand how the actual asynchronous model works, even when using await. But, in your example, you could do this:
async function someFunction() {
try {
let content = await Service.getContent();
// use content here
console.log(content);
return someValue;
} catch(e) {
// handle error here
console.log('errror', err)
return someOtherValue;
}
}
someFunction().then(val => {
// do something with val here
});
An important thing to understand is that while await appears to "block" the function execution until the promise resolves and makes it look like you can program synchronously again even with async operations, this is only partially true. The function execution itself is still asynchronous. In fact, as soon as you call await Service.getContent(), your function someFunction() returns a promise and any code after that function is called keeps on executing. So, the whole program flow is not blocked, only the internals of someFunction() are blocked waiting on that promise. await is really just syntactical sugar for .then(). The underlying concepts are still the same. And, functions still return immediately as soon as you await an asynchronous operation.
You can only use await inside an async function and all function declared async return a promise. So, the promise is still being used, await just gives you a bit more way to write code inside a function.

Chaining observables with flatMap

I'm working with observables and the flatMap operator, I wrote a method which makes and API call and returns an observable with an array of objects.
Basically what I need is to get that array of objects and process each object, after all items are processed. I want to chain the result to make an extra API call with another method that I wrote.
The following code does what I need:
this.apiService.getInformation('api-query', null).first().flatMap((apiData) => {
return apiData;
}).subscribe((dataObject) => {
this.processService.processFirstCall(dataObject);
}, null, () => {
this.apiService.getInformation('another-query', null).first().subscribe((anotherQueryData) => {
this.processService.processSecondCall(anotherQueryData);
});
});
But this approach isn't optimal from my perspective, I would like to do chain those calls using flatMap but if I do the following:
this.apiService.getInformation('api-query', null).first().flatMap((apiData) => {
return apiData;
}).flatMap((dataObject) => {
this.processService.processFirstCall(dataObject);
return [dataObject];
}).flatMap((value) => {
return this.apiService.getInformation('another-api-query', null).first();
}).subscribe((value) => {
this.processService.processSecondCall(value);
});
The second API call executes once for each item on the apiData array of objects. I know I'm missing or misunderstanding something. But from the second answer of this thread Why do we need to use flatMap?, I think that the second flatMap should return the processed apiData, instead is returning each of the object items on that Array. I would appreciate the help.
Thank you.
What you want is the .do() operator, and not flatMap(). flatMap() is going to transform an event to another event, and in essence, chaining them. .do() just executes whatever you instruct it to do, to every emission in the events.
From your code, there are 2 asynchronous methods (calls to the api) , and 2 synchronous (processService). What you want to do is :
Call to the first API (async), wait for the results
Process the results (sync)
Call to the second API (async), wait for the results to come back
Process the results (sync)
Hence your code should be :
this.apiService.getInformation('api-query', null)//step1
.first()
.do((dataObject) => this.processFirstCall(dataObject))//step2
.flatMap(() => this.apiService.getInformation('another-api-query', null))//step3
.first()
.do(value => this.processService.processSecondCall(value))//step4
.subscribe((value) => {
console.log(value);
});
I wrote in comment the steps corresponding to the list above. I also cleaned up unnecessary operators (like your first flatMap is kinda redundant).
Also, in the event you want to transform your data, then you should use .map() instead of .do(). And I think that is the place where you are confused with .map() and .flatMap().
The issue I think your encountering is that flatmap should be applied to an observable or promise. in your second code example, you are returning data within the flatmap operator which is then passed to the following flatmap functions, whereas these should be returning observables.
For example:
this.apiService.getInformation('api-query', null).first()
.flatMap((dataObject) => {
return this.processService.processFirstCall(dataObject);
}).flatMap((value) => {
return this.apiService.getInformation('another-api-query', null)
}).subscribe((value) => {
this.processService.processSecondCall(value);
});
See this post for futher clarification.

Get return value from subcribe on Observable

Using RxJS 5.0.0-rc.1, I'm trying to communicate my Observer and Observable in a way similar to how generators/iterators work by exchanging data using yield and .next(). The intention is to get a hold of what a call to .subscribe returns and modify/update following values in my observable stream depending on that.
I'm not entirely sure if this is, at all, possible. Though, I found out that you can catch exceptions thrown on .subscribe callbacks. The following snippets prints out "Boom!":
var source = Observable.create((observer) => {
try {
observer.next(42);
} catch (e) {
// This will catch the Error
// thrown on the subscriber
console.log(e.message);
}
observer.complete();
});
source.subscribe(() => {
throw new Error('Boom!');
});
So, what if instead of throwing, the subscriber returns a value? Is there a way for the Observable to retrieve it? Perhaps I'm approaching this the wrong way. If so, what's the "reactive" way of doing things in this scenario?
Many thanks.
EDIT
One possible way I came up with is by providing a callback function on every item in the stream. Something like:
var source = Observable.create((observer) => {
// This will print "{ success: true }"
observer.next({ value: 42, reply: console.log });
observer.complete();
});
source.subscribe(({ value, reply }) => {
console.log('Got', value);
return reply({ success: true });
});
Any other thoughts?
EDIT 2
Since my original question brought some confusion on what I was trying to achieve, I'll describe my real world scenario. I'm writing the API of a module for managing messages through queues (much like a simplified, in memory, AMQP-RPC mechanism) and I though RxJS would be a good fit.
It works like you would expect: a Publisher pushes messages to a queue, which get delivered to a Consumer. In term, the Consumer can reply to the Publisher, which can listen to that response if it's interested.
In an ideal scenario, the API would look something like this:
Consumer().consume('some.pattern')
.subscribe(function(msg) {
// Do something with `msg`
console.log(msg.foo);
return { ok: true };
});
Publisher().publish('some.pattern', { foo: 42 })
// (optional) `.subscribe()` to get reply from Consumer
That example would print 42.
The logic for replying to the Publisher lies within the Consumer function. But the actual response comes from the .subscribe() callback. Which leads me to my original question: how should I go about fetching that returned value from the creator of the stream?
Think of Consumer#consume() as:
/**
* Returns an async handler that gets invoked every time
* a new message matching the pattern of this consumer
* arrives.
*/
function waitOnMessage(observer) {
return function(msg) {
observer.next(msg);
// Conceptually, I'd like the returned
// object from `.subscribe()` to be available
// in this scope, somehow.
// That would allow me to go like:
// `sendToQueue(pubQueue, response);`
}
}
return Observable.create((observer) => {
queue.consume(waitOnMessage(observer));
});
Does it make any more sense?
There are indeed similarities between generators and observables. As you can see here, observables (asynchronous sequence of values) are the asynchronous version of iterables (synchronous sequence of values).
Now, a generator is a function which returns an Iterable. However, Rxjs Observable encloses both a generator - a.k.a producer (that you execute/start by calling subscribe) and the produced asynchronous sequence of values (that you observe by passing an Observer object). And the subscribe call returns a Disposable which allows you to stop receiving values (disconnect). So while generators and observables are dual concepts, the APIs to use them differ.
You cannot do two-way communication by default with the rxjs observable API. You probably could manage to do it by constructing yourself the back channel through subjects (note that you MUST have an initial value to kick off the cycle).
var backChannel = Rx.Subject();
backChannel.startWith(initialValue).concatMap(generateValue)
.subscribe(function observer(value){
// Do whatever
// pass a value through the backChannel
backChannel.next(someValue)
})
// generateValue is a function which takes a value from the back channel
// and returns a promise with the next value to be consumed by the observer.
You could consider wrapping that with :
function twoWayObsFactory (yield, initialValue) {
var backChannel = Rx.BehaviorSubject(initialValue);
var next = backChannel.next.bind(backChannel);
return {
subscribe : function (observer) {
var disposable = backChannel.concatMap(yield)
.subscribe(function(x) {
observer(next, x);
});
return {
dispose : function (){disposable.dispose(); backChannel.dispose();}
}
}
}
}
// Note that the observer is now taking an additional parameter in its signature
// for instance
// observer = function (next, yieldedValue) {
// doSomething(yieldedValue);
// next(anotherValue);
// }
// Note also that `next` is synchronous, as such you should avoir sequences
// of back-and-forth communication that is too long. If your `yield` function
// would be synchronous, you might run into stack overflow errors.
// All the same, the `next` function call should be the last line, so order of
// execution in your program is the same independently of the synchronicity of
// the `yield` function
Otherwise, the behaviour you describe seems to be that of an asynchronous generator. I never used such, but as this is a proposal for some future version of javascript, I think you can
already start trying it out with Babel (cf. https://github.com/tc39/proposal-async-iteration).
EDIT :
If you are looking for a loop-back mechanism (less general purpose approach but could very well fits your use case, if what you want to do is simple enough), the expand operator could help. To understand its behaviour, please check the doc, and the following answers on SO for examples of use in concrete contexts:
RxJS: Backpressure with switchMap producing N values
Circular Dependencies with RxJS. Modeling spores
How to build an rx poller that waits some interval AFTER the previous ajax promise resolves?
Basically expand allows you to both emit a value downstream and feed that value back at the same time in your producer.

Categories

Resources