I'm working on switching the code to rxjs
here is my original code.
userAuth$: BehaviorSubject<ArticleInfoRes>;
async loadArticleList(articleId: number) {
try {
const data = await this.articleApi.loadArticleList(articleId);
this.userAuth$.next(data);
return data;
} catch (error) {
return error;
}
}
Subsequently, an attempt was made to convert to rxjs, but the value was not delivered properly. A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
This is the code I tried to convert.
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
from(this.articleApi.loadArticleList(articleId)).subscribe(
data => {
this.userAuth$.next(data);
return data;
},
error => {
return error;
}
)
}
A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
To answer your question directly, the reason why the result (i.e., value or error) isn't passed to where you use loadArticleList() is because your RxJS pipeline is not built properly. For one, adding return data; and return error; in your subscription handlers won't actually return data nor error.
To get the result, you might want to "trickle down" (streamline) that result further down the RxJS pipeline, so that subscription to that result happens in the location you speak of, to where loadArticleList() is used. This translates to moving your call to .subscribe() outside of loadArticleList(), not inside.
So, here's a proper RxJS revision:
import { tap } from "rxjs/operators";
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
return from(this.articleApi.loadArticleList(articleId)).pipe(
tap((data) => this.userAuth$.next(data)) // 👈 tap into the result to update the next userAuth$
);
}
Using the tap RxJS operator, we can grab the result of loadAarticleList(articleId), process it according to your callback function inside tap (in this case, simply update the next value of userAuth$), and then with that same grabbed result, unmodified, pass it on to whoever subscribes to loadArticleList().
Finally, to actually "pass" data or errorto the location where the loadArticleList function is used, simply subscribe there.
// in your component's .ts somwehere, probably
loadArticleList(123).subscribe(
data => { /* do as you want with the result */ },
error => { /* add your error-handling logic here */ }
);
Related
I'm trying to learn pomise and I used code which I found in Internet... I don't understand everything. It looks for me very nasty but it works...
I initialized promise
function initialize(city) {
var options = {
url: 'https://api.weatherbit.io/v2.0//forecast/',
headers: {
'User-Agent': 'request'
}
};
return new Promise(function(resolve, reject) {
request.get(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
})
})
}
I don't understand why I need to put return after initializePromise. Is it possible to refactor the code without return?
var initializePromise = initialize("Berlin");
return initializePromise.then(function(result) {
weather = result;
var rain = result["data"][0]["precip"];
console.log(rain);
}, function(err) {
console.log(err);
})
This all depends upon what you want to do. If you wrote this version, which is slightly altered from your original but functionally the same:
function f1(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
}, function(err) {
console.log(err)
})
}
then when you call
f1('Berlin')
you would request the Berlin result from the server, When the server responds, you would either pass to console.log the error received from the request or turn the returned body into a JS object, extract the appropriate precip property from it, and log that to the console. The resulting Promise value returned from f1 is useless, and the weather variable is unused and unusable.
If you want to log that precipitation, but still keep a useful return value, you can write:
function f2(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
return weather // *** NOTE new line here ***
}, function(err) {
console.log(err)
})
}
This time calling with Berlin (and ignoring the error case from now on), you would log the precipitation returned, but also return a Promise for the whole Berlin weather node. That means you can still do this:
f2('Berlin')
to log Berlin's first precipitation value, but that now returns a useful value, so you could do
f2('Berlin').then(console.log)
to do that same logging, and then log the entire Berlin result.
Or you could do
f2('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
But now note the cleanup that is available. First of all the rain variable in f2 is only used on the next line, and the weather one is simply a reference to the original result argument. So we can simplify it this way:
function f3(city) {
return initialize(city).then(function(result) {
console.log(result["data"][0]["precip"])
return result
}, function(err) {
console.log(err)
})
}
This does the same thing more simply. But now there is a much more important simplification. It's quite possible that we don't need this function at all! If we have an error handler of some sort (even if it's just console.err), and we already have a function that does most of the weather handling we want, then instead of
f3('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
we can add the logging line from f3 into this first callbak, and get the same result by calling directly to initialize:
initialize('Berlin').then(function(weather) {
console.log(weather["data"][0]["precip"])
// do something useful with `weather` here.
}, errorHandler)
The reason this works is because initialize returns the result of calling then on the Promise, and then f2 and f3 also return either an altered value or the original one, keeping a Promise chain intact.
I would suggest that if you're in doubt you return something in any of these situations. It makes it much easier to continue working with values.
This issue has been driving me nuts for the last couple of days. I'm far from being a Javascript expert, maybe the solution is obvious but I don't see it.
What I basically try to do is :
download items in paralell, each request being made for a given
item type (type1, type2) with different properties.
Once downloaded, execute a callback function to post-process the data
(this is the same function with different parameters and with a test
on the item type in order to have different processing)
Save the item
If I download 1 item type, everything is OK. But if I download 2 types, then at some point in the processing loop within the first callback execution, exactly when the callback is executed a 2nd time for the 2nd type, then the test on type will indicate it's the 2nd type, while the items are of the 1st type...
Here is an extract of the code :
downloadType1();
downloadType2();
// 2nd argument of download() is the callback function
// 3rd argument is the callback function parameters
function downloadType1() {
// Some stuff here
let callbackParameters = ['type1', 'directory1'];
download('url', headacheCallback, callbackParameters);
}
function downloadType2() {
// Some the stuff here
let callbackParameters = ['type2', 'directory2'];
download('url', headacheCallback, callbackParameters);
}
async function download(url, callbackBeforeSave, callbackParameters) {
// Some stuff here
let response;
try {
response = await rp(url);
} catch (e) {
console.log("Error downloading data");
}
// Call callbacks before saving the data
if(callbackBeforeSave) {
let updatedResponse;
if (callbackParameters) {
updatedResponse = await callbackBeforeSave(response, ...callbackParameters);
} else {
updatedResponse = await callbackBeforeSave(response);
}
response = updatedResponse;
}
// Some stuff here with the post-processed data
}
async function headacheCallback(data, type, directory) {
for (item of data) {
// Some stuff here, include async/await calls (mostly to download and save files)
console.log(type, item.propertyToUpdate, item.child.propertyToUpdate);
// This is were my issue is.
// The test will success although I'm still the 'type1' loop. I know because the console.log above shows the item is indeed of type 'type1'
if (type === 'type2') {
item.child.propertyToUpdate = newUrl; // Will eventually fail because 'type1' items don't have a .child.propertyToUpdate property
} else {
item.propertyToUpdate = newUrl;
}
}
}
At some point, the output of console.log will be :
type2 <valueOfTheProperty> undefined which should be type2 undefined <valueOfTheProperty>...
A quick thought : in the first version of the callback, I used the arguments global variables in combination with function.apply(...). This was bad precisely because arguments was global and thus was changed after the 2nd call...
But now I don't see anything global in my code that could explain why type is changing.
Any help would be greatly appreciated.
Thanks!
I don't see anything global in my code that could explain why type is changing.
It's not type that is changing. Your problem is item that is an involuntary global:
for (item of data) {
// ^^^^
Make that a
for (const item of data) {
// ^^^^
And always enable strict mode!
This is a job for Promise.all
const p1 = new Promise((res, rej) => res());
Promise.all([p1, p2]).then((results) => results.map(yourFunction));
Promise.all will return an array of resolved or may catch on any rejection. But you don't have to reject if you setup your p1, p2, pn with a new Promise that only resolves. Then your function map can handle the branching and do the right thing for the right response type. Make sense?
I am working on a project where I am building a simple front end in Angular (typescript) / Node to make call to a back end server for executing different tasks. These tasks take time to execute and thus need to be queued on the back end server. I solved this issue by following the following tutorial: https://github.com/realpython/flask-by-example and everything seems to work just fine.
Now I am finishing things up on the front end, where most of the code has been already written in Typescript using Angular and Rxjs. I am trying to replicate the following code in Typescript:
https://github.com/dimoreira/word-frequency/blob/master/static/main.js
This code consists of two functions, where first function "getModelSummary"(getResults in the example) calls a post method via:
public getModelSummary(modelSummaryParameters: ModelSummaryParameters): Observable<ModelSummary> {
return this.http.post(`${SERVER_URL}start`, modelSummaryParameters)
.map(res => res.json())
;
}
to put the job in queue and assign a jobID to that function on the back end server. The second function "listenModelSummary", ideally should get executed right after the first function with the jobId as it's input and loops in a short interval checking if the job has been completed or not:
public listenModelSummary(jobID: string) {
return this.http.get(`${SERVER_URL}results/` + jobID).map(
(res) => res.json()
);
}
Once the job is done, it needs to return the results, which would update the front end.
I am new to Typescript, Observables and rxjs and wanted to ask for the right way of doing this. I do not want to use javascript, but want to stick to Typescript as much as possible in my front end code stack. How can I use the first function to call the second function with it's output "jobID" and have the second function run via interval until the output comes back?
Observables are great, and are the type of object returned by Angular's HttpClient class, but sometimes, in my opinion, dealing with them is a lot more complicated than using promises.
Yes, there is a slight performance hit for the extra operation to convert the Observable to a Promise, but you get a simpler programming model.
If you need to wait for the first function to complete, and then hand the returned value to another function, you can do:
async getModelSummary(modelSummaryParameters: ModelSummaryParameters): Promise<ModelSummary> {
return this.http.post(`${SERVER_URL}start`, modelSummaryParameters).toPromise();
}
async doStuff(): Promise<void> {
const modelSummary = await this.getModelSummary(params);
// not sure if you need to assign this to your viewmodel,
// what's returned, etc
this.listenModelSummary(modelSummary)
}
If you're dead-set on using Observables, I would suggest using the concatMap pattern, which would go something like this:
doStuff(modelSummaryParameters: ModelSummaryParameters): Observable<ModelSummary> {
return this.http
.post(`${SERVER_URL}start`, modelSummaryParameters)
.pipe(
concatMap(modelSummary => <Observable<ModelSummary>> this.listenModelSummary(modelSummary))
);
}
Here's an article on different mapping solutions for Observables: https://blog.angularindepth.com/practical-rxjs-in-the-wild-requests-with-concatmap-vs-mergemap-vs-forkjoin-11e5b2efe293 that might help you out.
You can try the following:
getModelSummary(modelSummaryParameters: ModelSummaryParameters): Promise<ModelSummary> {
return this.http.post(`${SERVER_URL}start`, modelSummaryParameters).toPromise();
}
async someMethodInYourComponent() {
const modelSummary = await this.get(modelSummary(params);
this.listenModelSummary(modelSummary)
}
// OR
someMethodInYourComponent() {
this.get(modelSummary(params).then(() => {
this.listenModelSummary(modelSummary);
});
}
After doing more reading/researching into rxjs, I was able to make my code work and I wanted to thank you guys for the feedback and to post my code below.
In my services I created two observables:
First one is to fetch a jobId returned by queue server:
// API: GET / FUNCTION /:jobID
public getModelSummaryQueueId(modelSummaryParameters: ModelSummaryParameters): Observable<JobId>{
return this.http.post(${SERVER_URL}start, modelSummaryParameters).map(
(jobId) => jobId.json()
)
}
Use the jobId from first segment to fetch data:
// API: GET / FUNCTION /:results
public listenModelSummary(jobId: JobId): Observable <ModelSummary>{
return this.http.get(${SERVER_URL}results/+ jobId).map(
(res) => res.json()
)
}
Below is the component that works with the 2 services above:
`
this.subscription = this.developmentService.getModelSummaryQueueId(this.modelSummaryParameters)
.subscribe((jobId) => {
return this.developmentService.listenModelSummary(jobId)
// use switchMap to pull value from observable and check if it completes
.switchMap((modelSummary) =>
// if value has not changed then invoke observable again else return
modelSummary.toString() === 'Nay!'
? Observable.throw(console.log('...Processing Request...'))
// ? Observable.throw(this.modelSummary = modelSummary)
: Observable.of(modelSummary)
)
.retryWhen((attempts) => {
return Observable
// specify number of attempts
.range(1,20)
.zip(attempts, function(i) {
return(i);
})
.flatMap((res:any) => {
// res is a counter of how many attempts
console.log("number of attempts: ", res);
res = 'heartbeat - ' + res
this.getProgressBar(res);
// this.res = res;
// delay request
return Observable.of(res).delay(100)
})
})
// .subscribe(this.displayData);
// .subscribe(modelSummary => console.log(modelSummary));
.subscribe((modelSummary) => {
console.log("FINAL RESULT: ", modelSummary)
this.modelSummary = modelSummary;
this.getProgressBar('Done');
});
});
`
I'm writing a Software that has the following flow:
Promise.resolve(updateMongoStatus)
.then(unzipFilesFromS3)
.then(phase4) //example
.then(phase5) //example
.then(processSomething)
.catch(saveErrorToMongo)
And I would like to know if it's ok to pass data around from the first function, to the last one, for example:
function updateMongoStatus() {
// do something here that updates Mongo and get status of some document
return { status }
}
function unzipFilesFromS3({ status }) {
// do something here to unzip files from s3
return { status, files }
}
function phase4({ status, files }) {
// etc
}
Until the processSomething finally gets called:
function processSomething({ parameterFromOutputOfUpdateMongoStatus, parameterFromPhase4, parameterFromPhase5 }) {
// Do something here
}
Is this ok? To pass data around like that?
Thank you.
Yes! This is totally ok, and for some people, this is the preferred way to pass data through a Promise chain (because it does not involve any globals / variables outside the scope of the Promise blocks).
In your case, since you want phase4, phase5, and mongo status in your last promise, you could do this:
Promise
.resolve(mongoStatus)
.then((mongoResult) => {
return unzipFilesFromS3().then(s3Result => {
return [s3Result, mongoResult];
});
})
.then(([ s3Result, mongoResult ]) => {
return Promise.all([
mongoResult,
s3Result,
phase4(mongoResult, s3Result)
]);
})
// repeat with phase5
.then(([ mongoResult, s3Result, phase4Result /* phase5, etc */ ]) => {
// etc
})
.catch(err => {});
No, you will need to pass a promise object from a thenable to the next thenable. If you simply pass a value, it will return the value.
When a value is simply returned from within a then handler, it will effectively return Promise.resolve().
Promise.prototype.then()
Update as of 14/03/2018: This answer is not correct. Please refer to comment from #Bergi
React/Redux n00b here :) - working with a crappy API that doesn't correctly return error codes (returns 200 even when end point is down), therefore is messing up my Ajax calls. Owner of the API will not able to correct this soon enough, so I have to work around it for now.
I'm currently checking each success with something like this (using lodash):
success: function(data) {
// check if error is REALLY an error
if (_.isUndefined(data.error) || _.isNull(data.error)) {
if (data.id) data.sessionId = data.id;
if (data.alias) data.alias = data.alias;
resolve(data || {});
} else {
reject(data); // this is an error
}
}
I want to move this into it's own function so that I can use it with any action that performs an Ajax call, but I'm not sure where to include this.
Should this type of function map to state (hence, treat it like an action and build a reducer) or should this be something generic outside of Redux and throw in main.js for example?
You can dispatch different actions depending on if the promise was resolved or not. The simplest way would be something like this:
function onSuccess(data) {
return {
type: "FETCH_THING_SUCCESS",
thing: data
};
}
function onError(error) {
return {
type: "FETCH_THING_ERROR",
error: error
};
}
function fetchThing(dispatch, id) {
// the ajax call that returns the promise
fetcher(id)
.then(function(data){
dispatch(onSuccess(data));
})
.catch(function(error) {
dispatch(onError(error));
});
}
Heres some more documentation how to do this kind of thing...