Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Removed @flow decorator; as it's type signature is misleading
  • Loading branch information
mweststrate committed Mar 12, 2018
1 parent 36d690c commit b0dbe24
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 173 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Expand Up @@ -11,14 +11,14 @@ This is the extensive list of all changes.
The changes mentioned here are discussed in detail in the [release highlights](https://medium.com/p/c1fbc08008da/), or were simply updated in the docs.

* MobX 4 introduces separation between the production and non production build. The production build strips most typechecks, resulting in a faster and smaller build. Make sure to substitute process.env.NODE_ENV = "production" in your build process! If you are using MobX in a react project, you most probably already have set this up. Otherwise, they idea is explained [here](https://reactjs.org/docs/add-react-to-an-existing-app.html).
* Observable maps are now backed by real ES6 Maps. This means that any value can be used as key now, not just strings and numbers.
* Introduced `flow` to create a chain of async actions. This is the same function as [`asyncActions`](https://github.com/mobxjs/mobx-utils#asyncaction) of the mobx-utils package. (Note, unlike `asyncAction` and unlike in the beta, it can _not_ be used as decorator)
* Introduced `decorate(thing, decorators)` to decorate classes or object without needing decorator syntax.
* Introduced `onBecomeObserved` and `onBecomeUnobserved`. These API's enable hooking into the observability system and get notified about when an observable starts / stops becoming used. This is great to automaticaly fetch data from external resources, or stop doing so.
* `computed` / `@computed` now accepts a `requiresReaction` option. If it set, the computed value will throw an exception if it is being read while not being tracked by some reaction.
* To make `requiresReaction` the default, use `mobx.configure({ computedRequiresReaction: true })`
* Introduced `mobx.configure({ disableErrorBoundaries })`, for easier debugging of exceptoins. By [NaridaL](https://github.com/NaridaL) through [#1262](https://github.com/mobxjs/mobx/pull/1262)
* `toJS` now accepts the options: `{ detectCycles?: boolean, exportMapsAsObjects?: boolean }`, both `true` by default
* Introduced `flow` to create a chain of async actions. This is the same function as [`asyncActions`](https://github.com/mobxjs/mobx-utils#asyncaction) of the mobx-utils package
* Observable maps are now backed by real ES6 Maps. This means that any value can be used as key now, not just strings and numbers.
* The flow typings have been updated. Since this is a manual effort, there can be mistakes, so feel free to PR!

* `computed(fn, options?)` / `@computed(options) get fn()` now accept the following options:
Expand Down
159 changes: 5 additions & 154 deletions src/api/flow.ts
@@ -1,13 +1,6 @@
import { BabelDescriptor } from "../utils/decorators2"
import { addHiddenFinalProp } from "../utils/utils"
import { action } from "./action"

// method decorator:
export function flow(
target: Object,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor
let generatorId = 0

// non-decorator forms
export function flow<R>(generator: () => IterableIterator<any>): () => Promise<R>
Expand Down Expand Up @@ -43,132 +36,11 @@ export function flow<A1, A2>(
generator: (a1: A1, a2: A2) => IterableIterator<any>
): (a1: A1, a2: A2) => Promise<any>
export function flow<A1>(generator: (a1: A1) => IterableIterator<any>): (a1: A1) => Promise<any>
// ... with name
export function flow<R>(name: string, generator: () => IterableIterator<any>): () => Promise<R>
export function flow<A1>(
name: string,
generator: (a1: A1) => IterableIterator<any>
): (a1: A1) => Promise<any> // Ideally we want to have R instead of Any, but cannot specify R without specifying A1 etc... 'any' as result is better then not specifying request args
export function flow<A1, A2, A3, A4, A5, A6, A7, A8>(
name: string,
generator: (
a1: A1,
a2: A2,
a3: A3,
a4: A4,
a5: A5,
a6: A6,
a7: A7,
a8: A8
) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8) => Promise<any>
export function flow<A1, A2, A3, A4, A5, A6, A7>(
name: string,
generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => Promise<any>
export function flow<A1, A2, A3, A4, A5, A6>(
name: string,
generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => Promise<any>
export function flow<A1, A2, A3, A4, A5>(
name: string,
generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => Promise<any>
export function flow<A1, A2, A3, A4>(
name: string,
generator: (a1: A1, a2: A2, a3: A3, a4: A4) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3, a4: A4) => Promise<any>
export function flow<A1, A2, A3>(
name: string,
generator: (a1: A1, a2: A2, a3: A3) => IterableIterator<any>
): (a1: A1, a2: A2, a3: A3) => Promise<any>
export function flow<A1, A2>(
name: string,
generator: (a1: A1, a2: A2) => IterableIterator<any>
): (a1: A1, a2: A2) => Promise<any>
export function flow<A1>(
name: string,
generator: (a1: A1) => IterableIterator<any>
): (a1: A1) => Promise<any>

/**
* `asyncAction` takes a generator function and automatically wraps all parts of the process in actions. See the examples below.
* `asyncAction` can be used both as decorator or to wrap functions.
*
* - It is important that `asyncAction should always be used with a generator function (recognizable as `function*` or `*name` syntax)
* - Each yield statement should return a Promise. The generator function will continue as soon as the promise settles, with the settled value
* - When the generator function finishes, you can return a normal value. The `asyncAction` wrapped function will always produce a promise delivering that value.
*
* When using the mobx devTools, an asyncAction will emit `action` events with names like:
* * `"fetchUsers - runid: 6 - init"`
* * `"fetchUsers - runid: 6 - yield 0"`
* * `"fetchUsers - runid: 6 - yield 1"`
*
* The `runId` represents the generator instance. In other words, if `fetchUsers` is invoked multiple times concurrently, the events with the same `runid` belong toghether.
* The `yield` number indicates the progress of the generator. `init` indicates spawning (it won't do anything, but you can find the original arguments of the `asyncAction` here).
* `yield 0` ... `yield n` indicates the code block that is now being executed. `yield 0` is before the first `yield`, `yield 1` after the first one etc. Note that yield numbers are not determined lexically but by the runtime flow.
*
* `asyncActions` requires `Promise` and `generators` to be available on the target environment. Polyfill `Promise` if needed. Both TypeScript and Babel can compile generator functions down to ES5.
*
* N.B. due to a [babel limitation](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/issues/26), in Babel generatos cannot be combined with decorators. See also [#70](https://github.com/mobxjs/mobx-utils/issues/70)
*
* @example
* import {asyncAction} from "mobx-utils"
*
* let users = []
*
* const fetchUsers = asyncAction("fetchUsers", function* (url) {
* const start = Date.now()
* const data = yield window.fetch(url)
* users = yield data.json()
* return start - Date.now()
* })
*
* fetchUsers("http://users.com").then(time => {
* console.dir("Got users", users, "in ", time, "ms")
* })
*
* @example
* import {asyncAction} from "mobx-utils"
*
* mobx.useStrict(true) // don't allow state modifications outside actions
*
* class Store {
* \@observable githubProjects = []
* \@state = "pending" // "pending" / "done" / "error"
*
* \@asyncAction
* *fetchProjects() { // <- note the star, this a generator function!
* this.githubProjects = []
* this.state = "pending"
* try {
* const projects = yield fetchGithubProjectsSomehow() // yield instead of await
* const filteredProjects = somePreprocessing(projects)
* // the asynchronous blocks will automatically be wrapped actions
* this.state = "done"
* this.githubProjects = filteredProjects
* } catch (error) {
* this.state = "error"
* }
* }
* }
*
* @export
* @returns {Promise}
*/
export function flow(arg1: any, arg2?: any): any {
// decorator
if (typeof arguments[1] === "string") return flowDecorator.apply(null, arguments)
export function flow(generator: Function) {
if (arguments.length !== 1)
fail(process.env.NODE_ENV && `Flow expects one 1 argument and cannot be used as decorator`)
const name = generator.name || "<unnamed flow>"

// direct invocation
const generator = typeof arg1 === "string" ? arg2 : arg1
const name = typeof arg1 === "string" ? arg1 : generator.name || "<unnamed async action>"
return createFlowGenerator(name, generator)
}

let generatorId = 0

export function createFlowGenerator(name: string, generator: Function) {
// Implementation based on https://github.com/tj/co/blob/master/index.js
return function() {
const ctx = this
Expand Down Expand Up @@ -216,24 +88,3 @@ export function createFlowGenerator(name: string, generator: Function) {
})
}
}

function flowDecorator(target: any, propertyName: string, descriptor: BabelDescriptor) {
return {
configurable: true,
enumerable: false,
get() {
addHiddenFinalProp(
this,
propertyName,
flow(
propertyName,
descriptor.initializer ? descriptor.initializer.call(this) : descriptor.value
)
)
return this[propertyName]
},
set(v: any) {
addHiddenFinalProp(this, propertyName, flow(propertyName, v))
}
}
}
9 changes: 4 additions & 5 deletions test/base/babel-tests.js
Expand Up @@ -1073,21 +1073,20 @@ test("actions are not reassignable", () => {
}).toThrow(/Cannot assign to read only property 'm4'/)
})

test("it should support asyncAction as decorator (babel)", async () => {
test("it should support asyncAction (babel)", async () => {
const values = []

mobx.configure({ enforceActions: true })

class X {
@observable a = 1

@mobx.flow
f = function*(initial) {
f = mobx.flow(function* f(initial) {
this.a = initial // this runs in action
this.a += yield Promise.resolve(5)
this.a = this.a * 2
return Promise.resolve(this.a)
}
return this.a
})
}

const x = new X()
Expand Down
13 changes: 6 additions & 7 deletions test/base/flow.js
Expand Up @@ -89,15 +89,15 @@ test("it should support throw from yielded promise generator", done => {
)
})

test("it should support asyncAction as decorator", done => {
test("it should support asyncAction in classes", done => {
const values = []

mobx.configure({ enforceActions: true })

class X {
a = 1;
a = 1

*f(initial) {
f = mobx.flow(function*(initial) {
this.a = initial // this runs in action
try {
this.a = yield delay(100, 5, true) // and this as well!
Expand All @@ -107,11 +107,10 @@ test("it should support asyncAction as decorator", done => {
this.a = e
}
return this.a
}
})
}
mobx.decorate(X, {
a: mobx.observable,
f: mobx.flow
a: mobx.observable
})

const x = new X()
Expand All @@ -132,7 +131,7 @@ test("it should support logging", done => {
const events = []
const x = mobx.observable({ a: 1 })

const f = mobx.flow("myaction", function*(initial) {
const f = mobx.flow(function* myaction(initial) {
x.a = initial
x.a = yield delay(100, 5)
x.a = 4
Expand Down
9 changes: 4 additions & 5 deletions test/base/typescript-tests.ts
Expand Up @@ -1558,21 +1558,20 @@ test("promised when can be cancelled", async () => {
}
})

test("it should support asyncAction as decorator (babel)", async () => {
test("it should support asyncAction as decorator (ts)", async () => {
const values = []

mobx.configure({ enforceActions: true })

class X {
@observable a = 1;
@observable a = 1

@mobx.flow
*f(initial: number): any {
f = mobx.flow(function* f(initial: number): any {
this.a = initial // this runs in action
this.a += yield Promise.resolve(5)
this.a = this.a * 2
return this.a
}
})
}

const x = new X()
Expand Down

0 comments on commit b0dbe24

Please sign in to comment.