-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async/Await Support #176
Async/Await Support #176
Conversation
Also while working on this I noticed your instanbul config is off, and these builds are silently failing: https://travis-ci.org/pburtchaell/redux-promise-middleware/builds/282257458#L608-L651 Also, this needs yet another babel config so that it stops compiling async/await to its own thing. I've tested that locally but haven't included it in the PR. |
@mikew Thanks! I'll take a look into this later this week. 👍 |
This is now fixed on master. Would you mind rebasing your branch to master, @mikew? |
Can do! |
* origin/master: Remove known vulnerabilities badge Fix Istanbul tests Update dev dependencies
I merged instead of rebasing, since a rebase would require |
That's fine! Next step here is to add some documentation. Could you add a guide to the docs directory? After that, I'll start on a code review. 👍 |
Just a heads up that you might want to rebase/merge again. I recently pushed some updates to the docs. |
…-middleware into async-await-support * 'master' of https://github.com/pburtchaell/redux-promise-middleware: Rename file Update documentation Add comments Update examples Reorder gitignore and npmignore
Should I rewrite the chaining actions docs since it's now handled internally? |
Actually the semantics are just different enough in a couple of examples that, so I think I'll leave them be. |
No, I don't think this change warrants removing any content/guides from the docs. I think there should just be a new section for use with async functions. This, and examples where ever else it would make sense. Would you agree? |
Agreed, and sorry it's taking me so long. |
Hi @mikew - this looks like a really useful change, can't wait until it's merged! Would you like any help writing the documentation? |
Oh gosh that would be lovely. I've added what I have to the PR: https://github.com/pburtchaell/redux-promise-middleware/pull/176/files#diff-596e1efe9b79c33a5a05e9b0fe30f33f What's there may even suffice. |
Something that I didn't mention in there, is that when you don't use an |
I'd like to see some clarity about the semantics between the return value (resolve of the async func) and the thunk like dispatch. What's the order that these will dispatch in according to the middleware. const foo = () => ({
type: 'TYPE',
async payload(dispatch) {
const filteredResult = await getResultAsync()
.then(result => {
dispatch(foo(result))
return filter(result)
})
dispatch(bar())
return filteredResult
}
}); Here I'd expect 4 actions to be dispatched
What would the order be? |
Not sure why you'd expect FOO and BAR to be dispatched after TYPE_FULFILLED, if that were the case you'd never be able to use the resolved value from the function in the _FULFILLED action. |
@mikew I don't expect that order, I'm asking what the order would be. But to play devil's advocate, how would I dispatch an action after fulfilled though? If I did want to? |
The phrase "Here I'd expect 4 actions to be dispatched" is a little confusing then :) It would be:
|
I came with absolutely no expectations about the order and used an unordered list!... I'm just a wide eyed curious cat asking! I couldn't put them in an order when listing them! |
I feel like the PENDING would happen after the FOO and the BAR because the payload would be invoked by the middleware before the pending was dispatched?... |
You can't, you'd have to use redux-thunk, which gives you 100% control of the flow of actions. I don't think that's any different than this middleware currently works. |
Yes, these latest versions work that way... grumbles to self and continues to support version 2 |
That's actually a good catch, and I didn't write any specs to verify the behaviour in that case. |
Are you stores dependant on data existing in other stores? That seems like a recipe for disaster. I'd move that to the component layer, and if the data that it requires from two stores isn't available yet, well, you're still loading. |
const foo = () => ({
type: 'TYPE',
async payload(dispatch) {
const filteredResult = await getResultAsync()
.then(result => {
dispatch(foo(result))
return filter(result)
})
dispatch(bar())
return filteredResult;
}
});
@tomatau I think it makes sense to dispatch the Given the design principle async action objects describe the promise state, this order makes sense to me.
Let's just make sure this is well documented! |
The PR would need to be changed for this to be the case. I feel like this PR/feature is a bit confusing even in a basic example and also a bit restrictive in what you can do with it all in order to support something that kinda looks cool? Unless I'm missing something? |
Yep, lines 44-45 need to be moved to call the async function after the pending action is dispatched on lines 106-110.
In my opinion, at a bare minimum, it does make sense to support async functions, which is where this PR started. I do agree there is some complexity/confusion, though. I think that comes from (1) providing |
The problem is with transpiled async functions. If they're not transpiled, we can tell ahead of time if it's an async function. But if it's transpiled, we need to call the function to tell if it's async or not. And if we call the function, any actions it dispatches will be dispatched before we can do |
So I can see a few ways of handling this:
|
What's the purpose of this comparison, in that case?
Full disclaimer, I don't use async functions in practice and don't know the ins and outs of browser support and how it's transpiled (assuming this is with Babel?).
What do you think of removing const foo = () => ({
type: 'TYPE',
async payload() {
const result = await doSomething();
return result;
}
}); This will help move this PR along IMO. In another PR and/or issue, we can talk about how to introduce |
Yes, Babel or TypeScript will transpile asynchronous functions to regular old functions that return a promise. If they aren't transpiled, you can actually determine if they are asynchronous or not ahead of time, which is what that line is doing. In retrospect, that line is probably useless, because the other promise check will work. But if you ever wanted to treat them differently, that would be the place. I'm cool with ditching the dispatch/getState bits, but I'm also one of the people who was never affected by #67, and would just solve it another way. |
if (typeof promise === 'function') {
const functionResult = promise();
if (isPromise(functionResult)) {
..
}
} This change makes sense: check if the payload is a function. If true, call the function and check if the return is a promise. I have two concerns though. First, the // Yes, this is an antipattern...
// ...but, if someone does implement it, it would be called and have unexpected results.
const foo = () => ({
type: 'TYPE',
payload() {
...
}
}); Second, we must call the async function to see if a promise is returned.
This means the function is called before the pending action is dispatched. This this an issue @tomatau brought up earlier when he asked about the order in which actions are dispatched. I don't think this makes sense. Given this silly example (using Redux Thunk): const fooAction = () => {
return dispatch => {
return dispatch({
type: 'FOO',
async payload() {
dispatch({
type: 'BAR'
});
return true;
}
});
};
}; The dispatched action order is:
EDIT: I should mention, yes, this is the current behavior, but I think if we implement async functions—where someone is more likely to dispatch multiple async actions in a chain—we should change the order in which the actions are dispatched. So, in conclusion, is there another way to can detect an async function? The If there isn't another way, I worry we might not be able to implement this until async functions are supported by browsers. |
Also, I'm sorry for the delay! I was away this past week and just getting back. |
That's what this covers:
Nope. In fact it looks like checking for AsyncFunction is a faux pas: tc39/proposal-async-await#78 |
This PR adds support for native async / await functions.
AsyncFunction
(as they are in Node and modern browsers)dispatch
andgetState
from redux, just likeredux-thunk
.So a (maybe silly) example from the guide goes from this:
to this: