From 4d7fd5eb0b28dfbdfe08bfb58b17bb1652e1f2ce Mon Sep 17 00:00:00 2001 From: Mathew Heard Date: Tue, 29 Nov 2022 22:02:28 +1100 Subject: [PATCH] add Q.safetyAwait a function that preserves stack when continuing promises around other promises --- README.md | 46 ++++++++++++++++++++++++++++++++++ index.js | 35 ++++++++++++++++++++++++++ test/q-tests.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/README.md b/README.md index 01f7a60..3ae33f6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ # q-lite Lightweight implementation of Q.js for performance + +## Methods + +### Q.safetyAwait + +Solves stack preservation when calling async functions without an await over async boundries + +``` +function testFn(){ + const deferred = Q.defer() + async function a(){ + await Q.delay(1) + throw new Error('test') + } + const promise = a() + + // will fail if this becomes awaited + // this is because the stack is not preserved + await Q.delay(10) + + const p = Q.cancelledRace([deferred.promise,promise]) + deferred.resolve() + return await p +} +``` + +Stack would be lost (`testFn`) in this test over the `Q.delay` call. However with `Q.safetyAwait` method state can be preserved. + +``` +function testFn(){ + const deferred = Q.defer() + async function a(){ + await Q.delay(1) + throw new Error('test') + } + const promise = a() + + // will fail if this becomes awaited + // this is because the stack is not preserved + await Q.safetyAwait([Q.delay(10)], [promise]) + + const p = Q.cancelledRace([deferred.promise,promise]) + deferred.resolve() + return await p +} +``` \ No newline at end of file diff --git a/index.js b/index.js index 1758a83..2311138 100644 --- a/index.js +++ b/index.js @@ -415,4 +415,39 @@ Q.singularize = function(fn){ } } + +async function safeyAwait(cancelFn, primaryValues, secondaryValues){ + let dd + for(let i = 0; i < secondaryValues.length; i++){ + dd = Q.defer() + const promise = secondaryValues[i] + promise.then(dd.resolve, dd.reject) + dd.promise.resolve = dd.resolve + secondaryValues[i] = dd.promise + } + + let deferred = dd + if(!deferred) { + deferred = Q.defer() + deferred.promise.resolve = deferred.resolve + secondaryValues.push(deferred.promise) + } + cancelFn(()=>deferred.reject(Q.CancellationError)) + + try { + await Promise.race([...primaryValues, ...secondaryValues]) + } finally { + for(const p of secondaryValues){ + p.resolve() + } + } +} + +Q.safeyAwait = function(primaryValues, secondaryValues) { + let cancel + const ret = safeyAwait(c=>cancel=c, primaryValues, secondaryValues) + ret.cancel = cancel + return ret +} + module.exports = Q \ No newline at end of file diff --git a/test/q-tests.js b/test/q-tests.js index 8660047..f9e18a0 100644 --- a/test/q-tests.js +++ b/test/q-tests.js @@ -39,5 +39,71 @@ describe('Q tests', function(){ expect(ex.stack.toString()).to.contain('testFn') } }) + it('should preserve stack (2)', async function(){ + async function testFn(){ + const deferred = Q.defer() + async function a(){ + await Q.delay(1) + throw new Error('test') + } + const promise = a() + + // will fail if this becomes awaited + // this is because the stack is not preserved + + //await Q.delay(10) + const p = Q.cancelledRace([deferred.promise,promise]) + deferred.resolve() + return await p + } + try { + await testFn() + } catch(ex){ + expect(ex.stack.toString()).to.contain('testFn') + } + }) + it('should preserve stack (3)', async function(){ + async function testFn(){ + const deferred = Q.defer() + async function a(){ + await Q.delay(1) + throw new Error('test') + } + const promise = a() + + // unlike test 2 this will work, because the stack is preserved, however if promise is never resolved then promise will leak + await Promise.race([Q.delay(10), promise]) + + const p = Q.cancelledRace([deferred.promise,promise]) + deferred.resolve() + return await p + } + try { + await testFn() + } catch(ex){ + expect(ex.stack.toString()).to.contain('testFn') + } + }) + it('should preserve stack (4)', async function(){ + async function testFn(){ + const deferred = Q.defer() + async function a(){ + await Q.delay(1) + throw new Error('test') + } + const promise = a() + + await Q.safeyAwait([Q.delay(10)], [promise]) + + const p = Q.cancelledRace([deferred.promise,promise]) + deferred.resolve() + return await p + } + try { + await testFn() + } catch(ex){ + expect(ex.stack.toString()).to.contain('testFn') + } + }) }) }) \ No newline at end of file