From 307be95a3a70e33111a7a5b8565d3e2fd2d0a36c Mon Sep 17 00:00:00 2001 From: Sofiane Date: Mon, 27 Jul 2020 13:03:14 +0100 Subject: [PATCH 1/7] Catch errors thrown by the function result of the call helper --- src/helpers/__tests__/handleGenerator.spec.js | 18 ++++++ src/helpers/handleGenerator.js | 62 ++++++++++--------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index 07212c5..0d9e228 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -30,4 +30,22 @@ describe('Given the handleGenerator helper', function () { }); }); }); + + it("should catch errors in the function result of the call helper", function () { + const mistake = () => { + throw new Error("oops"); + }; + + const generator = function* () { + try { + yield call(mistake); + } catch (err) { + return yield call(() => err.message); + } + }; + + handleGenerator({}, generator(), (result) => + expect(result).to.be.equal("oops") + ); + }); }); \ No newline at end of file diff --git a/src/helpers/handleGenerator.js b/src/helpers/handleGenerator.js index fd4f61e..7828dea 100644 --- a/src/helpers/handleGenerator.js +++ b/src/helpers/handleGenerator.js @@ -28,39 +28,43 @@ export default function handleGenerator(machine, generator, done, resultOfPrevio return iterate(generatorThrow(generator, new Error(error))); } - const funcResult = func(...args); + try { + const funcResult = func(...args); - if (!funcResult) { - handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine); - iterate(generatorNext(generator)); - return; - } - - // promise - if (typeof funcResult.then !== 'undefined') { - funcResult.then( - result => { - handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, result); - return iterate(generatorNext(generator, result)); - }, - error => { - handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, error); + if (!funcResult) { + handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine); + iterate(generatorNext(generator)); + return; + } + + // promise + if (typeof funcResult.then !== 'undefined') { + funcResult.then( + result => { + handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, result); + return iterate(generatorNext(generator, result)); + }, + error => { + handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, error); + return iterate(generatorThrow(generator, error)); + } + ); + // generator + } else if (typeof funcResult.next === 'function') { + try { + cancelInsideGenerator = handleGenerator(machine, funcResult, generatorResult => { + handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, generatorResult); + iterate(generatorNext(generator, generatorResult)); + }); + } catch (error) { return iterate(generatorThrow(generator, error)); } - ); - // generator - } else if (typeof funcResult.next === 'function') { - try { - cancelInsideGenerator = handleGenerator(machine, funcResult, generatorResult => { - handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, generatorResult); - iterate(generatorNext(generator, generatorResult)); - }); - } catch (error) { - return iterate(generatorThrow(generator, error)); + } else { + handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, funcResult); + iterate(generatorNext(generator, funcResult)); } - } else { - handleMiddleware(MIDDLEWARE_GENERATOR_RESUMED, machine, funcResult); - iterate(generatorNext(generator, funcResult)); + } catch (error) { + return iterate(generatorThrow(generator, error)); } // a return statement of the normal function From e4a7834601ea388de76bab36bf24d4af271111c4 Mon Sep 17 00:00:00 2001 From: Sofiane Date: Mon, 17 Aug 2020 17:42:24 +0100 Subject: [PATCH 2/7] Test sync functions in nested generators --- src/helpers/__tests__/handleGenerator.spec.js | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index 0d9e228..2100c23 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -31,9 +31,9 @@ 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* () { @@ -45,7 +45,29 @@ describe('Given the handleGenerator helper', function () { }; handleGenerator({}, generator(), (result) => - expect(result).to.be.equal("oops") + expect(result).to.be.equal('oops') + ); + }); + + it('should catch errors thrown by synchronous functions in nested generators', function () { + const mistake = () => { + throw new Error('oops'); + }; + + const nestedGenerator = function* () { + yield call(mistake); + }; + + const generator = function* () { + try { + yield call(nestedGenerator); + } catch (err) { + return yield call(() => err.message); + } + }; + + handleGenerator({}, generator(), (result) => + expect(result).to.be.equal('oops') ); }); }); \ No newline at end of file From 7a35757450bee163209966ed8e1711e7957d83ae Mon Sep 17 00:00:00 2001 From: Sofiane Date: Mon, 17 Aug 2020 17:48:33 +0100 Subject: [PATCH 3/7] Remove unnecessary use of call helper --- src/helpers/__tests__/handleGenerator.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index 2100c23..b1dfd99 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -40,7 +40,7 @@ describe('Given the handleGenerator helper', function () { try { yield call(mistake); } catch (err) { - return yield call(() => err.message); + return err.message; } }; @@ -62,7 +62,7 @@ describe('Given the handleGenerator helper', function () { try { yield call(nestedGenerator); } catch (err) { - return yield call(() => err.message); + return err.message; } }; From 72333c51754d68c949502bdb1e83892fb3cfc107 Mon Sep 17 00:00:00 2001 From: Sofiane Date: Tue, 18 Aug 2020 11:04:53 +0100 Subject: [PATCH 4/7] Add test for async throwns in nested generator --- src/helpers/__tests__/handleGenerator.spec.js | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index b1dfd99..519461f 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -58,16 +58,55 @@ describe('Given the handleGenerator helper', function () { yield call(mistake); }; + const afterThrow=sinon.spy() + const generator = function* () { try { yield call(nestedGenerator); + yield call(afterThrow); } catch (err) { return err.message; } }; - handleGenerator({}, generator(), (result) => - expect(result).to.be.equal('oops') - ); - }); + expect(afterThrow.notCalled).to.be.equal(true); + + const onGeneratorEnds = sinon.spy(); + + handleGenerator({}, generator(), onGeneratorEnds); + + expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); + }); + + it('should catch errors thrown by asynchronous functions in nested generators', function (done) { + const mistake = () => { + throw new Error('oops'); + }; + + const nestedGenerator = function* () { + yield call(mistake) + }; + + const afterThrow=sinon.spy() + + const generator = function* () { + 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); + }); }); \ No newline at end of file From d2312b072560f8720a04ef1ecc5dffcb781ad433 Mon Sep 17 00:00:00 2001 From: Sofiane Date: Tue, 18 Aug 2020 11:05:18 +0100 Subject: [PATCH 5/7] Don't iterate if no result --- src/helpers/handleGenerator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/handleGenerator.js b/src/helpers/handleGenerator.js index 7828dea..6b67214 100644 --- a/src/helpers/handleGenerator.js +++ b/src/helpers/handleGenerator.js @@ -13,7 +13,7 @@ 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); From d47390a5bd12c176c09caf442f167b140dd5ba12 Mon Sep 17 00:00:00 2001 From: Sofiane Date: Mon, 24 Aug 2020 06:11:02 +0100 Subject: [PATCH 6/7] Add handleGenerator tests --- src/helpers/__tests__/handleGenerator.spec.js | 282 +++++++++++++++++- 1 file changed, 270 insertions(+), 12 deletions(-) diff --git a/src/helpers/__tests__/handleGenerator.spec.js b/src/helpers/__tests__/handleGenerator.spec.js index 519461f..7b280eb 100644 --- a/src/helpers/__tests__/handleGenerator.spec.js +++ b/src/helpers/__tests__/handleGenerator.spec.js @@ -36,7 +36,7 @@ describe('Given the handleGenerator helper', function () { throw new Error('oops'); }; - const generator = function* () { + const generator = function* generator() { try { yield call(mistake); } catch (err) { @@ -49,18 +49,40 @@ describe('Given the handleGenerator helper', function () { ); }); + 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* () { + const nestedGenerator = function* nestedGenerator () { yield call(mistake); }; - const afterThrow=sinon.spy() + const afterThrow = sinon.spy(); - const generator = function* () { + const generator = function* generator() { try { yield call(nestedGenerator); yield call(afterThrow); @@ -69,27 +91,51 @@ describe('Given the handleGenerator helper', function () { } }; + 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); - - expect(onGeneratorEnds).to.be.calledOnce.and.to.be.calledWith('oops'); - }); + + 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 = () => { + const mistake = async () => { throw new Error('oops'); }; - const nestedGenerator = function* () { - yield call(mistake) + const nestedGenerator = function* nestedGenerator () { + yield call(mistake); }; - const afterThrow=sinon.spy() + const afterThrow = sinon.spy(); - const generator = function* () { + const generator = function* generator() { try { yield call(nestedGenerator); yield call(afterThrow); @@ -108,5 +154,217 @@ describe('Given the handleGenerator helper', 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 From d57616395a1cf35e0d149e20c9be5abe2ede03ff Mon Sep 17 00:00:00 2001 From: Sofiane Date: Mon, 24 Aug 2020 06:15:11 +0100 Subject: [PATCH 7/7] Make use of the yield* keyword in nested generators --- src/helpers/handleGenerator.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/helpers/handleGenerator.js b/src/helpers/handleGenerator.js index 6b67214..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); @@ -14,7 +15,7 @@ export default function handleGenerator(machine, generator, done, resultOfPrevio const iterate = function (result) { 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); } };