const posts = [{
title: 'Post One',
body: 'This is Post 1'
},
{
title: 'Post Two',
body: 'This is Post 2'
},
{
title: 'Post Three',
body: 'This is Post 3'
}
]
I create the posts array here
function getPosts() {
setTimeout(() => {
let output = '';
posts.forEach((post) => {
output += `<li>${post.title}</li>`;
});
document.body.innerHTML = output;
}, 1000);
}
First I get the post by get Post. Inorder to make it like api request I make with setTimeoutFunction();
function CreatePost(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
posts.push(post);
const error = false;
if (!error) {
resolve();
} else {
reject('Error Something Went wrong')
}
}, 2000)
})
}
I create the fourth post with CreatePost
function CreateAnotherPost(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
posts.push(post);
const error = false;
if (!error) {
resolve(console.log(posts));
} else {
reject('something went wrong')
}
}, 5000);
})
}
Here I create another post with the very long wait time
CreateAnotherPost({
title: 'Post Five',
body: 'This is post Five'
}).then(CreatePost({
title: 'Post Four',
body: 'This is post Four'
})).then(getPosts).catch(error => console.log(error));
I can make it run smoothly with chaining .then. But I don't know how to use promise.all
Promise.all takes a array of promises and waits for all the promises in the array to resolve and returns resolved promoise, if any error occurs in any one of the promise in array, it reached catch block
You can try this approach
Promise.all([CreateAnotherPost({
title: 'Post Five',
body: 'This is post Five'
}),
CreatePost({
title: 'Post Four',
body: 'This is post Four'
})
]).then(getPosts).catch(error => console.log(error));
Promise.all takes, an array of promises and executes them in order, synchronously,
If one first promise resolves, then it executes the second promise.
If any promise rejects then it will go to .catch block of Promise.all.
If all promises resolves successfully, then it will go to the .then block of Promise.all
Promise.all([
this.CreateAnotherPost({title: 'Post Five',body: 'This is post Five'}),
this.CreatePost({title: 'Post Four',body: 'This is post Four'})
])
.then((res)=>{
// todo after all promise resolves successfully
})
.catch((err)=>{
// todo after if any of the promises resolves rejects
})
Hope this helps!
You use Promise.all() when you have more than one promise and you want to run those asynchronous operations in parallel. To use it, you just pass Promise.all() an array of promises:
Promise.all([
CreatePost({title: 'Post Four', body: 'This is post Four'}),
CreateAnotherPost({title: 'Post Five', body: 'This is post Five'})
]).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
The results are then provided in an array that is ordered according to the array of input promises (results come from promises that resolve with a value which yours don't for some unknown reason - that's how you should be communicating the async result). If any of the promises rejects, Promise.all() will reject and you will only get the rejected reason, not any of the other results.
Related
In my Ionic app, I apply FirebaseX plugin (https://github.com/dpa99c/cordova-plugin-firebasex) and use its method fetchDocumentInFirestoreCollection to access a document from my Firestore (assume the document does exist properly). It successfully passes the success callback function inside the method but the returned document object is never accessed. I don't know how to access it actually. Here are my two used approaches to access:
await this.firebase.fetchDocumentInFirestoreCollection(
someDocID,
'someCollection',
() => {
console.log('fetchFirestoreCollection successfully'); // this can be printed
},
error => {
console.error('error in fetchFirestoreCollection', error);
}
).then(
doc => {
// Not enter this block ever
console.log(doc);
}
);
const doc = await this.firebase.fetchDocumentInFirestoreCollection(
someDocID,
'someCollection',
() => {
console.log('fetchFirestoreCollection successfully'); // this can be printed
},
error => {
console.error('error in fetchFirestoreCollection', error);
}
);
But both of these two cannot access the returned document. How should I do?
Thank you.
In the #ionic-native/firebase-x/ngx/index.d.ts, change line 437 from
fetchDocumentInFirestoreCollection(documentId: string, collection: string, success: () => void, error: (err: string) => void): Promise<any>;
to
fetchDocumentInFirestoreCollection(documentId: string, collection: string, success: (value: object) => void, error: (err: string) => void): Promise<any>;
I am trying to return a promise from dispatch so that I can do something like this in my react component
this.props.dispatch(requestLogin(data))
.then((res) => {
Navigate.toHome()
}).catch((err) => {
this.showErrorMessage()
})
currently I wrapped my fetch to reuse the common things i pass on the server API and to put some logs for debugging. I did it like this:
export const query = (path, opts) => {
// common config and boilerplates here
// e.g add device id to every api request
return fetch(opts.url, reqOpts)
.then((response) => {
console.log('response received')
if (response.ok) {
return response.json()
} else
console.log('response not ok')})
.then((respData) => {
if (respData.status === true) {
console.log('response success')
return respData
} else {
const errObj = respData
errObj.server = true
throw errObj
}
}).catch((err) => {
console.log('error catched')
if (err.server) {
throw err
}
throw { status: false, errors: { error_code: 'ERR_FATAL', error_msg: 'Something went wrong.' }, err }
})
then my action creator is like this:
export function requestLogin (data) {
return function (dispatch) {
const opts = {
method: 'POST',
body: data,
}
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
},
).catch((err) => {
// this is not executed
return err
})
}
}
whats happening is
this.props.dispatch(requestLogin(data))
.then((res) => {
// the error actually goes here
Navigate.toHome()
}
(err) => {
// not here
}).catch((err) => {
// or here
this.showErrorMessage()
})
First, it's important to understand that the second argument you give then(onFulfilled, onRejected), which is onRejected, is another syntax to catch, so because it's written before your catch in the action creator, you get to there when the query function throws an error. that is why the catch block isn't executed. (read about promise's then).
after you catch your error in onRejected, it returns a promise, which is not an error anymore(the promise's state is fulfilled and not rejected).
if you want the promise to get to the catch block, you should change your action creator:
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return new Promise((resolve,reject) => {
reject(data2)
}
})
that will return a promise, which is rejected, so it will be caught by the catch block.
also, you can change the
return new Promise((resolve,reject) => {
reject(data2)
}
with
throw 'error'
or
Promise.reject(data2)
let me know if you need any further explanation.
When you doing:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
})
.catch((err) => {
// this is not executed
return err
})
It's actually, you do catch the error of query function already, then you return data2. It means you want to return a Promise success (resolve) with data2. The same thing happen with catch.
To fix it, you just need to remove the (data2) => {} and the catch block.
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
The second way, in case you still want to do something with the error before, you need to return Promise.reject:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
.catch((err) => {
// you can do something with error, and still return a promise.reject here
console.log('I found an error here', err)
return Promise.reject(err)
})
How can I continue to pass the payload to the rest of my operators?
For example:
login = (action$: ActionsObservable) => {
return action$.ofType(SessionActions.LOGIN_USER)
.mergeMap(({payload}) => {
return this.http.post(`${BASE_URL}/auth/login`, payload)
.map(result => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: result.json().meta
}))
.catch(error => Observable.of({
type: SessionActions.LOGIN_USER_ERROR
}));
});
}
How can I pass the payload and the result to the map operator?
The value for payload is available inside the .map because the function is defined with payload in scope. You can use payload within that function with the way it is currently written:
.map(result => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: {
one: result.json().meta,
two: payload /* this is in parent scope from mergeMap */
}
}))
However, if you want to make it more explicit, one way is to use zip to combine observables:
login = (action$: ActionsObservable) => {
return action$.ofType(SessionActions.LOGIN_USER)
.mergeMap(({payload}) => {
return Rx.Observable.zip(
Rx.Observable.of(payload),
this.http.post(`${BASE_URL}/auth/login`, payload),
(payload, result) => { payload, result }
)
.map({ payload, result} => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: result.json().meta
}))
.catch(error => Observable.of({
type: SessionActions.LOGIN_USER_ERROR
}));
});
}
Zip lets you combine several observables and provide a selector function to form the resulting object. Depending on your intentions, combineLatest may suit your needs for similar problems, when you want the latest value of each source whenever any source emits.
I am doing the following code and unable to figure out that why the data I am obtaining through AJAX is not being assigned to the class variable which is this.users
Code Snippet
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
console.log(this.users) // Printing 'undefined'
return this.users;
}
Any help will be much appreciated. This (http://prntscr.com/cal2l1) is link to my console output.
It is an asynchronous call, so you don't fetch data right away. However, if you setTimeout() on console.log(), it will be printed correctly because printing will occur after the data is fetched:
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
setTimeout(() => {
console.log(this.users) // Printing 'undefined'
}, 1000);
return this.users;
}
Reason for Problem
Well, it was really a silly mistake which I was making here. Since, getUsers() was being called after the DOM was loaded so it was assigning the value to class variable which is this.users after loading of DOM which restricted my page to load the required values at page loading stage (not after page loading).
Solution
Angular2 comes with a hook called OnInit or ngOnInit(). I was supposed to call the function in this event as follows.
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res},
err => console.error(err),
() => console.log('done')
);
console.log(this.users)
return this.users;
}
ngOnInit() {
getUsers();
}
Documentaion of OnInit: https://angular.io/docs/ts/latest/api/core/index/OnInit-class.html
Also the following documentation came up as a helping tool:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning