Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
added support for async functions closes #9
Browse files Browse the repository at this point in the history
  • Loading branch information
alidcast committed Apr 9, 2017
1 parent a1c6263 commit 90cbb46
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 33 deletions.
4 changes: 2 additions & 2 deletions src/plugin/task-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export default function createTaskInstance(data, subscriber) {
},

/**
* To finalize an instance that was called with the `keepRunning` binding,
* we call the resulting handle method returned by the stepper.
* To finalize an instance called with the `keepRunning` binding,
* we call the resulting handle method returned by the stepper.
*/
destroy() {
if (!this.isFinished) this.cancel()
Expand Down
15 changes: 14 additions & 1 deletion src/plugin/task-property.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export default function createTaskProperty(host, operation, autorun = true) {
run(...params) {
if (!scheduler) scheduler = createTaskScheduler(this, policy)
this.isAborted = false
let instanceData = { params, operation: operation.bind(host, ...params) },
let boundOperation = reflectBind(host, operation, params),
instanceData = { params, operation: boundOperation },
ti = createTaskInstance(instanceData, subscriber)
if (autorun) scheduler.schedule(ti)
return ti
Expand All @@ -75,3 +76,15 @@ export default function createTaskProperty(host, operation, autorun = true) {
...subscriptions
}
}

/**
* Function neutral bind operation.
*
* If `bind` is used on a gen function, it changes its prototype to `Function`.
* So we make sure to set the prototype of each operation back to its original.
*/
function reflectBind(ctx, operation, args) {
let boundOperation = operation.bind(ctx, ...args)
Reflect.setPrototypeOf(boundOperation, operation)
return boundOperation
}
73 changes: 47 additions & 26 deletions src/plugin/task-stepper.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObj } from '../util/assert'
import { isObj, isGen } from '../util/assert'

/**
A {Stepper} is responsible for iterating through the generator function.
Expand All @@ -14,9 +14,11 @@ import { isObj } from '../util/assert'
* @constructs Task Stepper
*/
export default function createTaskStepper(ti, callbacks) {
let iter = ti._operation(),
keepRunning = ti.options.keepRunning,
cancelablePromise
let iter,
cancelablePromise,
keepRunning = ti.options.keepRunning

if (isGen(ti._operation)) iter = ti._operation()

return {
async handleStart() {
Expand Down Expand Up @@ -75,39 +77,58 @@ export default function createTaskStepper(ti, callbacks) {
},

/**
* Recursively iterates through generator function until the operation is
* either canceled, rejected, or resolved.
* Wrapper for task's operation.
*
* When the operation is finished, we return the resulting handle method
* so that it can be executed or returned, in the cases where the final
* callback needs to be deferred for later handling.
* Runs operation, updates state, and either executes resulting callback
* or, if operation is kept running, defers it for later handling.
*/
async stepThrough() {
let stepper = this

async function takeAStep(prev = undefined) {
let value, done
if (ti.isCanceled) return stepper.handleEnd(stepper.handleCancel) // CANCELED / PRE-START

if (!ti.hasStarted) await stepper.handleStart() // STARTED

try {
({ value, done } = await stepper.handleYield(prev))
} catch (err) { // REJECTED
return stepper.handleError.bind(stepper, err)
}
const resultCallback = isGen(ti._operation) // RESOLVED / REJECTED / CANCELED
? await stepper._iterThrough()
: await stepper._syncThrough()

if (keepRunning) return stepper.handleEnd.bind(stepper, resultCallback)
else return stepper.handleEnd(resultCallback)
},

if (isObj(value) && value._cancel_) cancelablePromise = value
if (ti.isCanceled) return stepper.handleCancel // CANCELED / POST-YIELD
/**
* Recursively iterates through generator function until the operation is
* either resolved, rejected, or canceled.
* @return resulting handle callback
*/
async _iterThrough(prev = undefined) {
let value, done, stepper = this

value = await value
if (done) return stepper.handleSuccess.bind(stepper, value) // RESOLVED
else return await takeAStep(value)
try {
({ value, done } = await stepper.handleYield(prev))
} catch (err) { // REJECTED
return stepper.handleError.bind(stepper, err)
}

if (ti.isCanceled) return stepper.handleEnd(stepper.handleCancel) // CANCELED / PRE-START
if (!ti.hasStarted) await stepper.handleStart() // STARTED
const resultCallback = await takeAStep() // RESOLVED / REJECTED / CANCELED
if (isObj(value) && value._cancel_) cancelablePromise = value
if (ti.isCanceled) return stepper.handleCancel // CANCELED / POST-YIELD

if (keepRunning) return stepper.handleEnd.bind(stepper, resultCallback)
else return stepper.handleEnd(resultCallback)
value = await value
if (done) return stepper.handleSuccess.bind(stepper, value) // RESOLVED
else return await this._iterThrough(value)
},

/**
* Awaits the async function until the operation is either resolved
* or rejected. (Cancelation is not an option with async functions.)
* @return resulting handle callback
*/
async _syncThrough() {
let stepper = this
return ti._operation()
.then(val => stepper.handleSuccess.bind(stepper, val))
.catch(err => stepper.handleError.bind(stepper, err))
}
}
}
2 changes: 1 addition & 1 deletion src/util/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function isNamedFn(fn) {
return isFn(fn) && fn.name !== 'undefined' && fn.name !== ''
}

export function isGenFn(fn) {
export function isGen(fn) {
return fn.constructor.name === 'GeneratorFunction'
}

Expand Down
2 changes: 1 addition & 1 deletion test/unit/coverage/lcov-report/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ <h1>
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Sat Apr 08 2017 17:52:25 GMT-0400 (EDT)
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Sun Apr 09 2017 13:58:40 GMT-0400 (EDT)
</div>
</div>
<script src="prettify.js"></script>
Expand Down
31 changes: 29 additions & 2 deletions test/unit/specs/task-stepper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function * exTask() {
return 'success'
}

describe('Task Stepper', function() {
describe('Task Stepper - Generator Functions', function() {
it('solves empty function', async () => {
let operation = function * () {},
ti = createTaskInstance({ operation }),
Expand Down Expand Up @@ -78,7 +78,6 @@ describe('Task Stepper', function() {
expect(ti.isRejected).to.be.true
expect(ti.isResolved).to.be.false
expect(ti.isCanceled).to.be.false
// TODO should rejected task still attempt to return value?
expect(ti.value).to.be.null
expect(ti.error).to.not.be.null
})
Expand Down Expand Up @@ -224,3 +223,31 @@ describe('Task Stepper', function() {
expect(ti.value).to.equal('Success')
})
})

describe('Task Stepper - Async Functions', function() {
it('resolves operation', async () => {
let operation = async function() {
return 'success'
},
ti = createTaskInstance({ operation }),
stepper = createTaskStepper(ti, subscriber)
await stepper.stepThrough()
expect(ti.isResolved).to.be.true
expect(ti.isRejected).to.be.false
expect(ti.value).to.equal('success')
expect(ti.error).to.be.null
})

it('rejects the task instance', async () => {
let operation = async function() {
return await sinon.stub().returns('failed').throws()()
},
ti = createTaskInstance({ operation }),
stepper = createTaskStepper(ti, subscriber)
await stepper.stepThrough()
expect(ti.isRejected).to.be.true
expect(ti.isResolved).to.be.false
expect(ti.value).to.be.null
expect(ti.error).to.not.be.null
})
})

0 comments on commit 90cbb46

Please sign in to comment.