Skip to content
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

feat: allow passing options hash and add @taskGroup & friends #8

Merged
merged 3 commits into from
Jun 19, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 297 additions & 13 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,304 @@
import { task as ecTask } from 'ember-concurrency';
import { computedDecorator } from "@ember-decorators/utils/computed";
import {
task as createTaskProperty,
taskGroup as createTaskGroupProperty
} from 'ember-concurrency';
import { computedDecoratorWithParams } from '@ember-decorators/utils/computed';
import { assert } from '@ember/debug';

/**
* This utility function assures compatibility with the Ember object model style
* and initializer syntax required by Babel 6.
*
* For native classes using the method shorthand style (TypeScript & Babel 7),
* this function will access the `value`. For legacy code it will get the value
* from the initializer.
*
* // Ember object model
* export default EmberObject.extend({
* @task
* someTask: function*() {}
* });
*
* // Class syntax with initializers
* export default class extends EmberObject {
* @task
* someTask = function*() {}
* }
*
* @param {PropertyDescriptor} desc
* @returns {object|null}
* @private
*/
function extractValue(desc) {
return desc.value ||typeof desc.initializer === 'function' && desc.initializer() || null;
if ('value' in desc) {
return desc.value;
}
if (typeof desc.initializer === 'function') {
return desc.initializer();
}

return null;
}

function taskify(desc) {
if (!desc.writable) {
throw new Error('ember-concurrency-decorators does not support using getters and setters');
}
/**
* Takes a `PropertyDescriptor` and turns it into an ember-concurrency
* `TaskProperty`.
*
* @param {PropertyDescriptor} desc
* @returns {TaskProperty}
* @private
*/
function createTaskFromDescriptor(desc) {
assert(
'ember-concurrency-decorators: Getters and setters are not supported for tasks.',
desc.writable
);

const value = extractValue(desc);
return (typeof value === 'function') ? ecTask(value) : value;
assert(
'ember-concurrency-decorators: Can only decorate a generator function as a task.',
typeof value === 'function'
);

return createTaskProperty(value);
}

export const task = computedDecorator((target, key, desc) => taskify(desc));
export const restartableTask = computedDecorator((target, key, desc) => taskify(desc).restartable());
export const dropTask = computedDecorator((target, key, desc) => taskify(desc).drop());
export const keepLatestTask = computedDecorator((target, key, desc) => taskify(desc).keepLatest());
export const enqueueTask = computedDecorator((target, key, desc) => taskify(desc).enqueue());
/**
* Takes a `PropertyDescriptor` and turns it into an ember-concurrency
* `TaskGroupProperty`.
*
* @param {PropertyDescriptor} desc
* @returns {TaskGroupProperty}
* @private
*/
function createTaskGroupFromDescriptor(desc) {
assert(
'ember-concurrency-decorators: Getters and setters are not supported for task groups.',
desc.writable
);
assert(
'ember-concurrency-decorators: Task groups can not accept values.',
!extractValue(desc)
);
return createTaskGroupProperty();
}

/**
* Applies the `options` provided using the chaining API on the given `task`.
*
* @param {object} options
* @param {TaskProperty|TaskGroupProperty} task
* @private
*/
const applyOptions = (options, task) =>
Object.entries(options).reduce((task, [key, value]) => {
if (value === true) {
return task[key]();
}
if (
typeof value === 'string' ||
typeof value === 'number' ||
Array.isArray(value)
) {
return task[key](value);
}
assert(
`ember-concurrency-decorators: Cannot apply option '${key}' of type ${typeof value} with value '${value}' to task. Either specify the option as 'true' or provide a numeric or string value.`
);
return task;
}, task);

/**
* Creates a decorator function that transforms the decorated property using the
* given `propertyCreator` and accepts an optional user provided options hash,
* that that will be merged with the `baseOptions`.
*
* @param {function} propertyCreator
* @param {object} [baseOptions={}]
* @private
*/
const createDecorator = (propertyCreator, baseOptions = {}) =>
computedDecoratorWithParams((target, key, desc, [userOptions]) =>
applyOptions(
Object.assign({}, baseOptions, userOptions),
propertyCreator(desc)
)
);

/**
* Turns the decorated generator function into a task.
*
* Optionally takes a hash of options that will be applied as modifiers to the
* task. For instance `maxConcurrency`, `on`, `group` or `keepLatest`.
*
* ```js
* import EmberObject from '@ember/object';
* import { task } from 'ember-concurrency-decorators';
*
* class extends EmberObject {
* @task
* *plainTask() {}
*
* @task({ maxConcurrency: 5, keepLatest: true, cancelOn: 'click' })
* *taskWithModifiers() {}
* }
* ```
*
* @function
* @param {object?} [options={}]
* @return {TaskProperty}
*/
export const task = createDecorator(createTaskFromDescriptor);

/**
* Turns the decorated generator function into a task and applies the
* `restartable` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task.
*
* @function
* @param {object?} [options={}]
* @return {TaskProperty}
*/
export const restartableTask = createDecorator(createTaskFromDescriptor, {
restartable: true
});

/**
* Turns the decorated generator function into a task and applies the
* `drop` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task.
*
* @function
* @param {object?} [options={}]
* @return {TaskProperty}
*/
export const dropTask = createDecorator(createTaskFromDescriptor, {
drop: true
});

/**
* Turns the decorated generator function into a task and applies the
* `keepLatest` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task.
*
* @function
* @param {object?} [options={}]
* @return {TaskProperty}
*/
export const keepLatestTask = createDecorator(createTaskFromDescriptor, {
keepLatest: true
});

/**
* Turns the decorated generator function into a task and applies the
* `enqueue` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task.
*
* @function
* @param {object?} [options={}]
* @return {TaskProperty}
*/
export const enqueueTask = createDecorator(createTaskFromDescriptor, {
enqueue: true
});

/**
* Turns the decorated property into a task group.
*
* Optionally takes a hash of options that will be applied as modifiers to the
* task group. For instance `maxConcurrency` or `keepLatest`.
*
* ```js
* import EmberObject from '@ember/object';
* import { task taskGroup } from 'ember-concurrency-decorators';
*
* class extends EmberObject {
* @taskGroup({ maxConcurrency: 5 }) someTaskGroup;
*
* @task({ group: 'someTaskGroup' })
* *someTask() {}
*
* @task({ group: 'someTaskGroup' })
* *anotherTask() {}
* }
* ```
*
* @function
* @param {object?} [options={}]
* @return {TaskGroupProperty}
*/
export const taskGroup = createDecorator(createTaskGroupFromDescriptor);

/**
* Turns the decorated property into a task group and applies the
* `restartable` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task group.
*
* @function
* @param {object?} [options={}]
* @return {TaskGroupProperty}
*/
export const restartableTaskGroup = createDecorator(
createTaskGroupFromDescriptor,
{
restartable: true
}
);

/**
* Turns the decorated property into a task group and applies the
* `drop` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task group.
*
* @function
* @param {object?} [options={}]
* @return {TaskGroupProperty}
*/
export const dropTaskGroup = createDecorator(createTaskGroupFromDescriptor, {
drop: true
});

/**
* Turns the decorated property into a task group and applies the
* `keepLatest` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task group.
*
* @function
* @param {object?} [options={}]
* @return {TaskGroupProperty}
*/
export const keepLatestTaskGroup = createDecorator(
createTaskGroupFromDescriptor,
{
keepLatest: true
}
);

/**
* Turns the decorated property into a task group and applies the
* `enqueue` modifier.
*
* Optionally takes a hash of further options that will be applied as modifiers
* to the task group.
*
* @function
* @param {object?} [options={}]
* @return {TaskGroupProperty}
*/
export const enqueueTaskGroup = createDecorator(createTaskGroupFromDescriptor, {
enqueue: true
});