I have merged three Observables via pipe() and then I'm converting it to a Promise so I can return it. I have tested the call with onRequest() and it works just fine. However with onCall() I keep getting this error:
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
I have tried a lot of things but all of them return the same error. Am I doing something wrong here?
export const getNotifications = functions.region('europe-west1').https.onCall((data, context) => {
const notificationsObs = rxjs.from(db.collection('notifications').where('receiverID', '==', 'jmwJLofoKpaQ4jligJKPc24UEe72').get());
const requestObs = (requestsRef: any) => rxjs.from(db.getAll(...requestsRef));
const packageObs = (packagesRef: any) => rxjs.from(db.getAll(...packagesRef));
const notifications: Notification[] = [];
return notificationsObs.pipe(
mergeMap(snapshot => {
//
return requestObs(requestsRef);
}),
mergeMap((requests) => { // kthen requests
//
return packageObs(packagesRef)
})
).toPromise().then(() => {
return { notifications: "test" };
}).catch(err => {err})
});
Swift
functions.httpsCallable("getNotifications").call() { (result, error) in
if let error = error as NSError? {
print(error)
}
print(result?.data) // nil
}
So somehow onCall() is not supported in europe-west1. I removed the region and it works fine.
Related
I am writing a hook to make a post request that returns two properties, sessionId and sessionData. I am using this hook in a component. My hook looks like this.
export const createOrder = async () => {
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}
And my component look like this
const myComponent = () => {
useEffect(() => {
createOrder().then(data => {
console.log("Session Data",data.sessionData)
console.log("FROM PAGE", data)
})
}, [])
return (
<div />
)
}
When I try to access data.sessionData on the component I get the error that sessionDta does not exist on type void. But If I check the logs on the console I get the same object on the component and on the hook. Also If I check on my component the typeof data I get an object.
Why I am getting this error?
You don't return anything from your catch block, so the return type of your function is Promise<WhateverTheTypeOfResponseIs | void> (N.B. async functions implicitly return a Promise, and if your postWithToken function doesn't return anything then it's just Promise<void>), depending on which code path happens.
In the future you can avoid unpleasant and slightly problematic to debug issues like this by giving your functions an explicit return type and in this case the compiler will let you know that your expectation was violated:
const postWithToken = async (route: string, token: string, obj: any): Promise<boolean> => {
try {
const resp = await fetch(
route,
{
method: 'POST',
body: JSON.stringify(Object.assign(obj, { token }))
},
)
return Boolean(resp.status < 400 && resp.status >= 200)
} catch (err) {
console.error(err)
return false
}
}
const API_ROUTES = {
PAYMENT_SESSION: 'whatever'
}
const testObject = {
token: ''
}
const token = ''
const createOrder = async (): Promise<boolean> => { // <-- squiggles
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}
Playground
The types in the example I created may be different (you'll need to sub with the actual types from your code) but you should get the idea. You can fix this by any of the following:
Explicitly return something of the correct type from the catch block.
Change your return type to Promise<CorrectType | undefined>.
Move the error handling to the caller as suggested by goto1 in the comments.
Also note that as goto1 points out in the comments on the question, your hook isn't actually a hook (which is fine, but be careful of terminology).
This sounds like a TypeScript issue, so I suggest to provide the proper return type for your createOrder function.
// Use the official type for SessionData, if you have it available,
// otherwise create a new type that properly matches its shape
type CreateOrderResult = {
sessionData: SessionData;
}
// No need for `async/await` here, just return the `postWithToken`
// and handle errors inside your component
export const createOrder = (): Promise<CreateOrderResult> => {
// ...
return postWithToken(
API_ROUTES.PAYMENT_SESSION,
token || '',
testObject
)
}
// MyComponent.tsx
const MyComponent = () => {
useEffect(() => {
createOrder()
.then(data => console.log(data.sessionData))
.catch(error => /* handle errors appropriately */)
}, [])
}
open tsconfig.json file and
change
"strict": true,
into
"strict": false,
this usually works for me
check out https://v2.vuejs.org/v2/guide/typescript.html for more information
I know there are similar questions, but I can't find the answer.
First, please tell me if I'm doing something really wrong
I need to populate my state with data from an API call. This was working fine with code above:
export const GetPlanets = async () => {
const planets = await axios.get(`${BASE_URL}`).catch((e) => {
console.error(e);
})
return planets.data.results
}
But then I needed to make a second call to several links from one json response filed, and I managed to make it work (don't know if it is the correct approach, though)
const GetPlanets = async () => {
let planetas = {}
await axios.get(`${PLANETS_URL}`)
.then((p) => {
planetas = p.data.results
return axios.get(`${FILMS_URL}`)
}).then((f) => {
planetas.films.forEach((v, i) => {
planetas[i].film = f
})
})
})
.catch((e) => {
console.error(e);
})
return planetas
}
This is my component file, where I try to get the object, like I was doing before
useEffect(() => {
const fetchPlanetas = async () => { // ME TRYING...
const planetas = await GetPlanets()
setPlanetas(planetas)
setToShow(planetas[0])
};
fetchPlanetas()
}, [])
But all I get is undefined
You're getting an array of undefined because .map() needs a return value. In both your .map() callbacks, you are not returning anything.
const results = [1, 2, 3, 4]
const results2 = results.map(elem => {
elem = elem + 1
})
console.log(results2)
But, even if you did return something in your .map() callback, GetFilms(f) is asynchronous, so you would not get the results of GetFilms() mapped into the array as you would expect.
You have a couple of options:
If you have access to the API, send the films data along with the rest of the data when you do your first request.
Use async/await and Promise.all() to get responses.
What am I misunderstanding about how to use the .subscribe function in combination with the .do function?
Here is my observable sequence:
lookupSubscriber = (text$: Observable<string>) =>
text$.debounceTime(300)
.distinctUntilChanged()
.do(() => this.searching = true)
.switchMap(term => {
var data = this._callApi(this.lookupSubscriberAPI, term)
.do(() => {
this.searchFailed = false;
this.searching = false;
})
.catch(() => {
this.searchFailed = true;
this.searching = false;
return Observable.of([]);
})
return data;
})
.do(() => this.searching = false);
If my _callApi function is as follows, it works:
_callApi(url: string, term: string) {
if (term === '') {
return of.call([]);
}
return map.call(this.dataService.get(url + term), response => {
var data = this._transformSubscriberInfo(response);
return data;
})
}
However, when I try to rewrite it with a subscribe function like this:
_callApi = (url: string, term: string) => {
return this.dataService.get(url + term)
.subscribe(
response => { this._transformSubscriberInfo(response) },
error => error.text(),
() => {
if (Logging.isEnabled.light) {
console.log('%c API Call Complete', Logging.normal.orange);
}
))
}
... then the data call succeeds, but I receive error: Property 'do' does not exist on type 'Subscription'.
Basically I am trying to catch errors and run an "always" function after the api call, as shown in the second version of _callApi.
The first version of _callApi seems to return an Observable while the second one returns a Subscription object. And Subscription does not expose do, exactly as the error message states.
What you may want to try is to use a version of do that accepts error and complete callbacks in addition to the next callback:
return this.dataService.get(url + term)
.map(response => this._transformSubscriberInfo(response))
.do(
response => { /* log response */ },
error => { /* log error */ },
() => { /* log completion */ }
);
It worth mentioning that do is not capable of transforming the source stream, the observable it returns contains the same values as the observable it is called on. That's why we need the line .map(response => this._transformSubscriberInfo(response)).
Also complete callback should not be confused for an "always" function: it gets called only when the source observable completes and it does not get called when the observable produces an error or gets unsubscribed.
I'm using a scraping function to get some data from a bunch of urls listed inside an array. Here is the following function :
function getNbShares(urls) {
return Promise.map(urls, request).map((htmlOnePage, index) => {
const $ = cheerio.load(htmlOnePage),
share = $('.nb-shares').html();
return {
url: urls[index],
value: share
};
}).catch(function (urls, err) {
return {
url: urls[index],
value: err
};
});
}
It's working fine, however the error handling isn't. What I would like is that when I have an error (either because the page doesn't load or if the DOM selector is wrong) the map function/request keep doing is job and it returns me the error (or null) as a value attached to the url in the final array object.
I think you just want to do that handling a bit earlier, within the mapping function; and I think you can avoid having two separate mapping operations; see comments:
function getNbShares(urls) {
return Promise.map(
urls,
url => request(url)
.then(htmlOnePage => { // Success, so we parse
const $ = cheerio.load(htmlOnePage), // the result and return
value = $('.nb-shares').html(); // it as an object with
return { url, value }; // `url` and `value` props
})
.catch(error => ({url, error})) // Error, so we return an
// object with `url` and
// `error` props
}
);
}
(I've assumed you're using ES2015+, as you were using arrow functions.)
I might opt to factor part of that out:
function getNbSharesFromHTML(html) {
const $ = cheerio.load(html);
return $('.nb-shares').html();
}
function getNbShares(urls) {
return Promise.map(
urls,
url => request(url)
.then(htmlOnePage => ({url, value: getNbSharesFromHTML(htmlOnePage)))
.catch(error => ({url, error}))
}
);
}
Possibly even smaller pieces:
function getNbSharesFromHTML(html) {
const $ = cheerio.load(html);
return $('.nb-shares').html();
}
function getNbSharesFromURL(url) {
return request(url)
.then(htmlOnePage => ({url, value: getNbSharesFromHTML(htmlOnePage)))
.catch(error => ({url, error}));
}
function getNbShares(urls) {
return Promise.map(urls, getNbSharesFromURL);
}
Playing with RxJS and React, I'm having problem of how to wait for data in Observable.fromPromise generated within map on another Observable.
I have async helper:
const dataStreamGenerator = (url = CLIENTS_DATA_URL) => (
Rx.Observable.fromPromise(fetch(url))
.flatMap(response => Rx.Observable.fromPromise(response.json()))
.catch(err => Rx.Observable.of(new Error(err)))
);
Then I have actions.fetchClients$ which is Rx.Subject:
actions.fetchClients$.map((url = CLIENTS_DATA_URL) => {
const ts = Date.now();
console.log('FETCH CLIENTS with: ', url);
return dataStreamGenerator(url);
}).map(val => {
console.log('GOT DATA IN REDUCER: ', val);
const error = (val instanceof Error) ? val.message : undefined;
const data = error ? undefined : val;
actions.receivedClientsData$.next({ data, error, ts: Date.now() });
return (state) => state;
})
(Yes, trying to mimick Redux in RxJS).
Whan I test the dataStreamGenerator, it works ok (with ava) and delivers data:
test('dataStreamGenerator', t => {
const ds$ = dataStreamGenerator(CLIENTS_DATA_URL);
return ds$.do((data) => {
t.is(data.length, 10);
});
});
(AVA automatically subscribe to observable and consume it, so there is no need to subscribe).
But the actions.fetchClients$.map((url = CLI... second map (starting... console.log('GOT DATA IN REDUCER: ', val);... is still getting the Observable and not the data from dataStream$.
I tried all possible combinations of map and flatMap in fetchClients$ code but still no luck.
My test code is:
test.only('fetchClients$', t => {
const initialState = {};
actions.fetchClients$.next('http://jsonplaceholder.typicode.com/users');
reducer$.subscribe(val => {
console.log('TEST SUBSCRIBE VAL: ', val);
t.pass();
});
actions.fetchClients$.next('http://jsonplaceholder.typicode.com/users');
});
I cant figure out how to wait to the Observable dataStreamGenerator(url); to emit data and not the Observable.
Thanks.
You need to flatten the results of what you returned from dataStreamGenerator.
actions.fetchClients$
//This is now a flatMap
.flatMap((url = CLIENTS_DATA_URL) => {
const ts = Date.now();
console.log('FETCH CLIENTS with: ', url);
return dataStreamGenerator(url);
});
The map operator delivers the value as-is down stream, whereas flatMap will flatten Observables, Arrays and Promises such that their values are what get propagated.
It works in the test case because you are directly subscribing to the Observable returned by dataStreamGenerator.