From c418759b092a8fdffb813c7460a6cde1b00a715b Mon Sep 17 00:00:00 2001 From: Nathan Hunzaker Date: Mon, 13 Nov 2017 09:56:28 -0500 Subject: [PATCH] Repo.parallel can accept objects (#452) This commit updates repo.parallel such that it can accept plain JavaScript objects. In this case, the return payload is an object who's keys match the shape provided. This also allows generator actions to yield objects --- packages/microcosm/docs/api/actions.md | 32 +++++++++++++++++-- packages/microcosm/src/action.js | 23 +++++++------ packages/microcosm/src/coroutine.js | 17 ++++++---- packages/microcosm/src/utils.js | 7 ++++ .../microcosm/test/unit/action/link.test.js | 16 ++++++++++ .../middleware/generator-middleware.test.js | 32 +++++++++++++++++++ 6 files changed, 109 insertions(+), 18 deletions(-) diff --git a/packages/microcosm/docs/api/actions.md b/packages/microcosm/docs/api/actions.md index c36d775e..7a01840f 100644 --- a/packages/microcosm/docs/api/actions.md +++ b/packages/microcosm/docs/api/actions.md @@ -210,10 +210,38 @@ function getUsers (ids) { yield ids.map(id => repo.push(getUser, id)) } } + +repo.push(getUsers, [ 1, 2 ]) +``` + +Alternatively, you may also yield an object, this is useful for stitching together records that may have data at different locations: + +```javascript +function getPost (id) { + return fetch(`/posts/${id}`) +} + +function getComments (id) { + return fetch(`/posts/${id}/comments`) +} + +function getPostWithComments (id) { + return function * (repo) { + let { post, comments } = yield { + post: repo.push(getPost, id), + comments: repo.push(getComments, id) + } + + post.comments = comments + + return post + } +} + +repo.push(getBlogPost, 1) ``` -If all actions resolve or cancel, the generator sequence -continues. +If all actions resolve or cancel, the generator sequence continues. ### Action status methods are auto-bound diff --git a/packages/microcosm/src/action.js b/packages/microcosm/src/action.js index aad2e017..4fe70a51 100644 --- a/packages/microcosm/src/action.js +++ b/packages/microcosm/src/action.js @@ -187,17 +187,20 @@ class Action extends Emitter { * Set up an action such that it depends on the result of another * series of actions. */ - link(actions: Action[]): this { - let outstanding = actions.length - let answers = [] + link(actions: *): this { + let keys = Object.keys(Object(actions)) + let outstanding = keys.length + let answers = Array.isArray(actions) ? [] : {} - if (actions.length === 0) { + if (keys.length <= 0) { return this.resolve(answers) } - actions.forEach(action => { + keys.forEach(key => { + let action = actions[key] + let onResolve = answer => { - answers[actions.indexOf(action)] = answer + answers[key] = answer outstanding -= 1 if (outstanding <= 0) { @@ -205,9 +208,11 @@ class Action extends Emitter { } } - action.onDone(onResolve) - action.onCancel(onResolve) - action.onError(this.reject) + action.subscribe({ + onDone: onResolve, + onCancel: onResolve, + onError: this.reject + }) }) return this diff --git a/packages/microcosm/src/coroutine.js b/packages/microcosm/src/coroutine.js index 1f4f1123..0659ce60 100644 --- a/packages/microcosm/src/coroutine.js +++ b/packages/microcosm/src/coroutine.js @@ -3,7 +3,7 @@ */ import Action from './action' -import { isFunction, isPromise, isGeneratorFn } from './utils' +import { isFunction, isPromise, isGeneratorFn, isPlainObject } from './utils' /** * Provide support for generators, performing a sequence of actions in @@ -28,7 +28,7 @@ function processGenerator(action: Action, body: GeneratorAction, repo: *) { } function progress(subAction: Action | Action[]): Action { - if (Array.isArray(subAction)) { + if (Array.isArray(subAction) || isPlainObject(subAction)) { return progress(repo.parallel(subAction)) } @@ -37,11 +37,14 @@ function processGenerator(action: Action, body: GeneratorAction, repo: *) { `Iteration of generator expected an Action. Instead got ${typeof subAction}` ) - subAction.onDone(step) - subAction.onCancel(action.cancel, action) - subAction.onError(action.reject, action) - - return subAction + return subAction.subscribe( + { + onDone: step, + onCancel: action.cancel, + onError: action.reject + }, + action + ) } step() diff --git a/packages/microcosm/src/utils.js b/packages/microcosm/src/utils.js index e31fa42d..86622da3 100644 --- a/packages/microcosm/src/utils.js +++ b/packages/microcosm/src/utils.js @@ -150,6 +150,13 @@ export function isObject(target: *): boolean { return !(!target || typeof target !== 'object') } +/** + * Is a value a POJO? + */ +export function isPlainObject(target: *) { + return isObject(target) && target.constructor == Object +} + /** * Is a value a function? */ diff --git a/packages/microcosm/test/unit/action/link.test.js b/packages/microcosm/test/unit/action/link.test.js index 549682a0..7da9116f 100644 --- a/packages/microcosm/test/unit/action/link.test.js +++ b/packages/microcosm/test/unit/action/link.test.js @@ -60,4 +60,20 @@ describe('Action link', function() { expect(action.status).toEqual('reject') }) + + describe('when given an object', function() { + it('returns an object', async function() { + let repo = new Microcosm() + + let action = repo.parallel({ + one: repo.push(() => delay(1, 20)), + two: repo.push(() => delay(2, 10)), + three: repo.push(() => delay(3, 15)) + }) + + let payload = await action + + expect(payload).toEqual({ one: 1, two: 2, three: 3 }) + }) + }) }) diff --git a/packages/microcosm/test/unit/middleware/generator-middleware.test.js b/packages/microcosm/test/unit/middleware/generator-middleware.test.js index af2a310b..6893e948 100644 --- a/packages/microcosm/test/unit/middleware/generator-middleware.test.js +++ b/packages/microcosm/test/unit/middleware/generator-middleware.test.js @@ -235,4 +235,36 @@ describe('Generator Middleware', function() { }) }) }) + + describe('when yielding an object', function() { + it('waits for all items to complete', function() { + expect.assertions(2) + + let add = n => n + let repo = new Microcosm() + + repo.addDomain('count', { + getInitialState() { + return 0 + }, + register() { + return { + [add]: (a, b) => a + b + } + } + }) + + function testReturnValue() { + return function*(repo) { + yield { one: repo.push(add, 1), two: repo.push(add, 2) } + } + } + + repo.push(testReturnValue).onDone(payload => { + expect(repo).toHaveState('count', 3) + + expect(payload).toEqual({ one: 1, two: 2 }) + }) + }) + }) })