diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index 0d9e228..7b280eb 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -31,21 +31,340 @@ describe('Given the handleGenerator helper', function () { }); }); - it("should catch errors in the function result of the call helper", function () { + it('should catch errors in the function result of the call helper', function () { const mistake = () => { - throw new Error("oops"); + throw new Error('oops'); }; - const generator = function* () { + const generator = function* generator() { try { yield call(mistake); } catch (err) { - return yield call(() => err.message); + return err.message; } }; handleGenerator({}, generator(), (result) => - expect(result).to.be.equal("oops") + expect(result).to.be.equal('oops') ); }); + + it('should handle synchronous functions in nested generators', function () { + const synchronous = () => 'synchronous'; + + const nestedGenerator = function* nestedGenerator () { + return yield call(synchronous); + }; + + const generator = function* generator() { + try { + return yield call(nestedGenerator); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('synchronous'); + }); + + it('should catch errors thrown by synchronous functions in nested generators', function () { + const mistake = () => { + throw new Error('oops'); + }; + + const nestedGenerator = function* nestedGenerator () { + yield call(mistake); + }; + + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(nestedGenerator); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(afterThrow.notCalled).to.be.equal(true); + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + }); + + it('should handle asynchronous functions in nested generators', function (done) { + const async = async () => await Promise.resolve('async'); + + const nestedGenerator = function* nestedGenerator () { + return yield call(async); + }; + + const generator = function* generator() { + try { + return yield call(nestedGenerator); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('async'); + done(); + }, 300); + }); + + it('should catch errors thrown by asynchronous functions in nested generators', function (done) { + const mistake = async () => { + throw new Error('oops'); + }; + + const nestedGenerator = function* nestedGenerator () { + yield call(mistake); + }; + + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(nestedGenerator); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + expect(afterThrow.notCalled).to.be.equal(true); + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + done(); + }, 30); + }); + + it('should catch errors thrown by asynchronous functions in deeply nested generators', function (done) { + const mistake = async () => { + throw new Error('oops'); + }; + + const deepGenerator = function* deepGenerator() { + yield call(mistake); + }; + + const nestedGenerator = function* nestedGenerator () { + yield call(deepGenerator); + }; + + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(nestedGenerator); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + expect(afterThrow.notCalled).to.be.equal(true); + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + done(); + }, 30); + }); + + it('should catch errors thrown by synchronous functions in deeply nested generators', function () { + const mistake = () => { + throw new Error('oops'); + }; + + const deepGenerator = function* deepGenerator() { + yield call(mistake); + }; + + const nestedGenerator = function* nestedGenerator () { + yield call(deepGenerator); + }; + + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(nestedGenerator); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(afterThrow.notCalled).to.be.equal(true); + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + }); + + it('should handle synchronous functions in deeply nested generators', function () { + const synchronous = () => 'synchronous'; + + const deepGenerator = function* deepGenerator() { + return yield call(synchronous); + }; + + const nestedGenerator = function* nestedGenerator () { + return yield call(deepGenerator); + }; + + const generator = function* generator() { + try { + return yield call(nestedGenerator); + } catch (err) { + return err.message; + } + }; + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('synchronous'); + }); + + it('should handle asynchronous functions in deeply nested generators', function (done) { + const async = async () => await Promise.resolve('async'); + + const deepGenerator = function* deepGenerator() { + return yield call(async); + }; + + const nestedGenerator = function* nestedGenerator () { + return yield call(deepGenerator); + }; + + const generator = function* generator() { + try { + return yield call(nestedGenerator); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('async'); + done(); + }, 300); + }); + + it('should handle asynchronous functions', function (done) { + const async = async () => await Promise.resolve('async'); + + const generator = function* generator() { + try { + return yield call(async); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('async'); + done(); + }, 300); + }); + + it('should catch errors in asynchronous functions', function (done) { + const mistake = async () => { + throw new Error('oops'); + }; + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(mistake); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + setTimeout(function () { + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + expect(afterThrow.notCalled).to.be.equal(true); + + done(); + }, 300); + }); + + it('should handle synchronous functions', function () { + const synchronous = () => 'synchronous'; + + const generator = function* generator() { + try { + return yield call(synchronous); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('synchronous'); + }); + + it('should catch errors thrown by synchronous functions', function () { + const mistake = () => { + throw new Error('oops'); + }; + + const afterThrow = sinon.spy(); + + const generator = function* generator() { + try { + yield call(mistake); + yield call(afterThrow); + } catch (err) { + return err.message; + } + }; + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(afterThrow.notCalled).to.be.equal(true); + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + }); }); \ No newline at end of file diff --git a/src/helpers/handleGenerator.js b/src/helpers/handleGenerator.js index 7828dea..2d5344a 100644 --- a/src/helpers/handleGenerator.js +++ b/src/helpers/handleGenerator.js @@ -1,6 +1,7 @@ import handleMiddleware from './handleMiddleware'; import { MIDDLEWARE_GENERATOR_STEP, MIDDLEWARE_GENERATOR_END, MIDDLEWARE_GENERATOR_RESUMED, ERROR_GENERATOR_FUNC_CALL_FAILED } from '../constants'; import updateState from './updateState'; +import { call } from '.'; export default function handleGenerator(machine, generator, done, resultOfPreviousOperation) { const generatorNext = (gen, res) => !canceled && gen.next(res); @@ -13,8 +14,8 @@ export default function handleGenerator(machine, generator, done, resultOfPrevio var cancelInsideGenerator; const iterate = function (result) { - if (canceled) return; - + if (canceled || !result) return; + if (!result.done) { handleMiddleware(MIDDLEWARE_GENERATOR_STEP, machine, result.value); @@ -51,14 +52,19 @@ export default function handleGenerator(machine, generator, done, resultOfPrevio ); // generator } else if (typeof funcResult.next === 'function') { - try { - cancelInsideGenerator = handleGenerator(machine, funcResult, generatorResult => { + const generatorDone = (generatorResult) => { handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, generatorResult); iterate(generatorNext(generator, generatorResult)); - }); - } catch (error) { - return iterate(generatorThrow(generator, error)); - } + }; + const nestedGenerator = function* nestedGenerator() { + try { + const result = yield* funcResult; + yield call(generatorDone, result); + } catch (error) { + iterate(generatorThrow(generator, error)); + } + }; + cancelInsideGenerator = handleGenerator(machine, nestedGenerator()); } else { handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, funcResult); iterate(generatorNext(generator, funcResult)); @@ -77,7 +83,7 @@ export default function handleGenerator(machine, generator, done, resultOfPrevio // the end of the generator (return statement) } else { handleMiddleware(MIDDLEWARE_GENERATOR_END, machine, result.value); - done(result.value); + if (typeof done === "function") done(result.value); } };