pull Promise result outside of a nested map react js - async-await

I'm working at a React app and I need to loop inside an array containing objects with this structure:
const servers = [
{
name: "Server A",
url: "https://server-one.com/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC",
subVersions: [
{
name: "Subversion A1",
ip: "https://10.4.20/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
},
{
name: "Subversion A2",
ip: "https://10.4.20/v1/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
}
]
},
// ... more servers obj with the same structure
]
and I need to fetch information about the server versions via api calls to the url (or ip) and return an array of objects that looks like this:
[
{
name: "Server A",
version: "1.0.1",
subVersions: [
{
name: "Subversion A1",
version: "1.0.0"
},
{
name: "Subversion A2",
version: "1.0.0"
},
]
}
]
I'm doing is the following: the fetch() method will call fetchVersion() (which returns the main server version), and then it maps inside all the subVersions to fetch them too.
I'm struggling to get the result.data of the subVersions fetch out of that nested map you can see below.
I've tried to:
return the data at every iteration
pushing the data inside an array and try to return it at the end of the iterations
returning the array of data or returning a new Promise that resolves the array of data
But nothing. I can see the right data at the most nested map, but outside I either get a
Promise { <pending> } array or an empty one.
Sorry if the code looks messy, I hope it makes sense.
const fetchVersion = server =>
axios
.get(server.url, {
headers: {
Authorization: `Bearer ${server.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
const fetchSubVersion = subVersion =>
axios
.get(subVersion.ip, {
headers: {
Authorization: `Bearer ${subVersion.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
Class Servers {
constructor(servers = []) {
this.servers = servers ;
}
fetch() {
// ==== this map below is the problematic part =====
const subVersions = this.servers.map(server => {
var subVersArr = server.subVersions.map(
async server.subVersions.map(subVersion =>
await fetchSubVersions(subVersion)
.catch(() => ({ data: {} }))
.then(result => new Promise(resolve => resolve(result.data)));
});
})
);
return Promise.all(subVersArr)
.catch(() => ({ data: {} }))
.then(data => {
console.log("data", data); // <- I see the data here correctly
return data;
});
};
// ======= till here ============
const fetches = this.servers.map(server =>
fetchVersion(server)
.catch(() => ({ data: {} }))
.then(result => {
console.log("subVersions", subVersions(server)); <- but not here
return {
name: server.name,
versions: result.data,
subVersions: subVersions(server), // should contain the result of the
problematic map above
}
}))
);
return Promise.all(fetches);
}
Thanks for the help!!

How are you calling your fetch function? You should await it wherever you are calling it.
Like this:
async dummyFunction() {
await fetch();
}

I finally figured it out. What I didn't have clear is that then() returns a promise itself, so all I had to just do subVersion: await subVersions(server) and the data is there.

Related

How to use RTK Query in createSlice?

I want to process the data that I get from the request in the slice.
Because not all slices are async (but work with the same data), transformResponse is not suitable.
Is there anything you can suggest?
My code example:
Some RTK Query
export const currencyApi = createApi({
reducerPath: 'currencyApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.apilayer.com/exchangerates_data' }),
endpoints: (build) => ({
fetchCurrencyRates: build.query<IApiResponse, string>({
query: (currency) => ({
url: '/latest',
params: {
base: currency
},
headers: {
apikey: *SomeApiKey*
}
})
})
})
})
Slice where I want to use data from RTK requests
const initialState: ICurrencyState = {
currencyRates: {},
availableCurrencyOptions: [],
fromCurrency: '',
toCurrency: '',
exchangeRate: 0,
error: null
}
export const currencySlice = createSlice({
name: 'currency',
initialState,
reducers: {
//
}
})
Use Hooks in Components
You can send the received data to the slice via useEffect. Something like this:
const { data } = useFetchCurrencyRatesQuery();
useEffect(() => {
if (data !== undefined) {
dispatch(...)
}
}, [data])

NextJS: `HYDRATION` action doesn't receive server payload when using `redux-observable`

Packages:
redux-observable#2.0.0-rc.2
rxjs latest
universal-rxjs-ajax dev branch
next-redux-wrapper latest
next.js latest
I have a simple Page with getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
store.dispatch({ type: 'ADD_DATA' });
// const response = await fetch('https://rickandmortyapi.com/api');
// const data = await response.json();
// store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
Action 'ADD_DATA' triggers action 'SERVER_ACTION':
export const AddDataEpic: Epic = (action$) =>
action$.pipe(
ofType('ADD_DATA'),
mergeMap((action) =>
request({ url: 'https://rickandmortyapi.com/api' }).pipe(
map((response) => {
return {
type: 'SERVER_ACTION',
payload: response.response.characters,
};
})
)
)
);
Inside the reducer in the case 'SERVER_ACTION': clause I receive the payload:
const server = (state: State = { data: null }, action: AnyAction) => {
switch (action.type) {
case HYDRATE: {
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
return {
...state,
...state.server,
...action.payload.server,
};
}
case 'SERVER_ACTION': {
console.log('SERVER_ACTION >', action.payload); // logs out "SERVER_ACTION > https://rickandmortyapi.com/api/character"
return {
...state,
...state.server,
data: action.payload,
};
}
default:
return state;
}
};
But the payload isn't passed to HYDRATE action:
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
If I dispatch the 'SERVER_ACTION' action from inside the getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
// store.dispatch({ type: 'ADD_DATA' });
const response = await fetch('https://rickandmortyapi.com/api');
const data = await response.json();
store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
The HYDRATE action inside the reducer receive the payload:
HYDRATE > { server: { data: 'https://rickandmortyapi.com/api/character' } }
I don't understand what's wrong with my code.
May it be a bug in one of the libraries? Or is it a mistake in my code?
If anyone has any suggestions, PLEASE
#PYTHON DEVELOPER999 It might be due to the latest update on next-redux-wrapper, there are few migration steps =>
https://github.com/kirill-konshin/next-redux-wrapper#upgrade-from-6x-to-7x

Nest.js handling errors for HttpService

I'm trying to test NestJS's built in HttpService (which is based on Axios). I'm having trouble testing error/exception states though. In my test suite I have:
let client: SomeClearingFirmClient;
const mockConfigService = {
get: jest.fn((type) => {
switch(type) {
case 'someApiBaseUrl': {
return 'http://example.com'
}
case 'someAddAccountEndpoint': {
return '/ClientAccounts/Add';
}
case 'someApiKey': {
return 'some-api-key';
}
default:
return 'test';
}
}),
};
const successfulAdd: AxiosResponse = {
data: {
batchNo: '39cba402-bfa9-424c-b265-1c98204df7ea',
warning: '',
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
const failAddAuth: AxiosError = {
code: '401',
config: {},
name: '',
message: 'Not Authorized',
}
const mockHttpService = {
post: jest.fn(),
get: jest.fn(),
}
it('Handles a failure', async () => {
expect.assertions(1);
mockHttpService.post = jest.fn(() => of(failAddAuth));
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: mockConfigService,
},
{
provide: HttpService,
useValue: mockHttpService,
},
SomeClearingFirmClient,
],
}).compile();
client = module.get<SomeClearingFirmClient>(SomeClearingFirmClient);
const payload = new SomeClearingPayload();
try {
await client.addAccount(payload);
} catch(e) {
console.log('e', e);
}
});
And my implementation is:
async addAccount(payload: any): Promise<SomeAddResponse> {
const addAccountEndpoint = this.configService.get('api.someAddAccountEndpoint');
const url = `${this.baseUrl}${addAccountEndpoint}?apiKey=${this.apiKey}`;
const config = {
headers: {
'Content-Type': 'application/json',
}
};
const response = this.httpService.post(url, payload, config)
.pipe(
map(res => {
return res.data;
}),
catchError(e => {
throw new HttpException(e.response.data, e.response.status);
}),
).toPromise().catch(e => {
throw new HttpException(e.message, e.code);
});
return response;
}
Regardless of whether I use Observables or Promises, I can't get anything to catch. 4xx level errors sail on through as a success. I feel like I remember Axios adding some sort of config option to reject/send an Observable error to subscribers on failures... but I could be imagining that. Am I doing something wrong in my test harness? The other StackOverflow posts I've seen seem to say that piping through catchError should do the trick, but my errors are going through the map operator.
Your mockHttpService seems to return no error, but a value:
mockHttpService.post = jest.fn(() => of(failAddAuth));
What of(failAddAuth) does is to emit a value(failAddAuth) and then complete.
That's why the catchError from this.httpService.post(url, payload, config) will never be reached, because no errors occur.
In order to make sure that catchError is hit, the observable returned by post() must emit an error notification.
You could try this:
// Something to comply with `HttpException`'s arguments
const err = { response: 'resp', status: '4xx' };
mockHttpService.post = jest.fn(() => throwError(err));
throwError(err) is the same as new Observable(s => s.error(err))(Source code).

RootQuery Resolve: calls a service in a separate js file

I am OK with javascript but I am very new to GraphQL. I currently have this GraphQL structure and it is working. I found examples online for how to get different types organized into SRP files. I am however unable to find how to do this with the resolve: as it requires a function.
GraphQL:
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
ownerData: {
type: OwnerType,
description: 'Get all owners',
args:{key: {type: GraphQLString} },
resolve: (obj, args) => {
const url = 'http://localhost:5001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
},
carData: {
type: carType,
description: 'Get owned vehicles',
args:{key: {type: GraphQLString} },
resolve: (obj, args) => {
const url = 'http://localhost:6001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
},
}
})
I can move the service calls into separate files but not sure how to structure the resolve as it needs a function.
Would it be something like this:
const VehicleService = require('./ExternalServices/Vehicles');
.....snip...
resolve: (obj, args) => { VehicleService.GetVehicles() }
Generally speaking, I've found the best way to keep my code organized is to put all of the business logic elsewhere, initialized into the context object. If you're using graphql-js directly (it'll be set up differently if you're using something like apollo-server, but the context is still the right place for this):
graphql.js excerpt
(dataloaders is the SRP logic here)
const { graphql } = require('graphql');
const dataloaders = require('./dataloaders');
const typeDefs = require('./type-definitions')
const schema = require('./schema')
exports.query = (whatever, args) => {
const context = {};
context.requestId = 'uuid-something';
context.loaders = dataloaders.initialize({ context });
return graphql(schema, query, null, context)
}
schema.js excerpt
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
ownerData: {
type: OwnerType,
description: 'Get all owners',
args:{key: {type: GraphQLString} },
resolve: (obj, args, context) => {
const key = args.key;
return context.loaders.owner({ key });
}
}
}
});
dataloaders.js excerpt
exports.initialize = ({ context }) => {
return {
owner({ key }) {
const url = 'http://localhost:6001/api/.../' + key
return fetch(url, { headers: { requestId: context.requestId }})
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
});
}
}
};
In addition to better code organization, doing it this way allows for easier testing, since your resolvers don't need any external dependencies. You can inject your dependencies this way by preloading the context with whatever you want for testing, and you can handle business logic where business logic belongs.
Initializing your business logic with the context of the request also allows you to adjust functionality based off the request: requestId (as I've shown), access control, etc.
The syntax () => {} is simply a function definition. The resolve field expects a function definition so it can run it when the field must be resolved.
You can move the resolving function to a different file like so:
car-data-resolve.js:
const resolveCarData = (obj, args) => {
const url = 'http://localhost:6001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
export default resolveCarData;
And then use it in your schema definition:
import resolveCarData from './car-data-resolve';
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
/* Other fields */
carData: {
type: carType,
description: 'Get owned vehicles',
args: { key: { type: GraphQLString } },
resolve: resolveCarData
},
},
});

How can I test Observable.ajax (redux-observable)?

I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Resources