-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(packages/sui-decorators): Create AsyncInlineError decorator
- Loading branch information
Showing
4 changed files
with
246 additions
and
1 deletion.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
packages/sui-decorators/src/decorators/AsyncInlineError/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import isPromise from '../../helpers/isPromise.js' | ||
|
||
const GENERIC_ERROR_MESSAGE = 'You might decorate an async function with use @AsyncInlineError' | ||
|
||
const _runner = ({instance, original} = {}) => { | ||
return function (...args) { | ||
const response = [] | ||
Object.defineProperty(response, '__INLINE_ERROR__', { | ||
enumerable: false, | ||
writable: true, | ||
value: true | ||
}) | ||
|
||
let promise = null | ||
|
||
try { | ||
promise = original.apply(instance, args) | ||
} catch (error) { | ||
throw new Error(GENERIC_ERROR_MESSAGE, {cause: error}) | ||
} | ||
|
||
if (!isPromise(promise)) { | ||
throw new Error(GENERIC_ERROR_MESSAGE) | ||
} | ||
|
||
return promise | ||
.then(r => { | ||
response[0] = null | ||
response[1] = r | ||
return response | ||
}) | ||
.catch(e => { | ||
response[0] = e | ||
response[1] = null | ||
return response | ||
}) | ||
} | ||
} | ||
|
||
export function AsyncInlineError(config = {}) { | ||
return function (target, fnName, descriptor) { | ||
const {value: fn, configurable, enumerable} = descriptor | ||
|
||
return Object.assign( | ||
{}, | ||
{ | ||
configurable, | ||
enumerable, | ||
value(...args) { | ||
const _fnRunner = _runner({ | ||
instance: this, | ||
original: fn | ||
}) | ||
|
||
return _fnRunner.apply(this, args) | ||
} | ||
} | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import {AsyncInlineError} from './decorators/AsyncInlineError/index.js' | ||
import {cache, invalidateCache} from './decorators/cache/index.js' | ||
import inlineError from './decorators/error.js' | ||
import streamify from './decorators/streamify.js' | ||
import tracer from './decorators/tracer/index.js' | ||
|
||
export {cache, invalidateCache, streamify, inlineError, tracer} | ||
export {AsyncInlineError, cache, invalidateCache, streamify, inlineError, tracer} |
92 changes: 92 additions & 0 deletions
92
packages/sui-decorators/test/browser/asyncInlineErrorSpec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import {expect} from 'chai' | ||
|
||
import {AsyncInlineError} from '../../src/index.js' | ||
|
||
describe('AsyncInlineError decorator', () => { | ||
it('should exist', () => { | ||
expect(AsyncInlineError).to.exist | ||
expect(AsyncInlineError).to.be.a('function') | ||
}) | ||
|
||
it('should return an array [null, resp] when the promise is resolved', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnASuccessPromise() { | ||
return Promise.resolve(true) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
expect(await buzz.returnASuccessPromise()).to.be.eql([null, true]) | ||
}) | ||
|
||
it('should return an array [Error, null] when the promise is rejected', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnAFailedPromise() { | ||
return Promise.reject(new Error('Error Rejected')) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
const [err, resp] = await buzz.returnAFailedPromise() | ||
expect(resp).to.be.eql(null) | ||
expect(err).to.be.an.instanceof(Error) | ||
expect(err.message).to.be.eql('Error Rejected') | ||
}) | ||
|
||
it('should preserve the context', async () => { | ||
class Buzz { | ||
name = 'Carlos' | ||
|
||
@AsyncInlineError() | ||
returnASuccessPromise() { | ||
return Promise.resolve(this.name) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
expect(await buzz.returnASuccessPromise()).to.be.eql([null, 'Carlos']) | ||
}) | ||
|
||
it('should works with an Error subclass', async () => { | ||
class CustomError extends Error {} | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnAFailedPromise() { | ||
return Promise.reject(new CustomError('Error Rejected')) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
const [err, resp] = await buzz.returnAFailedPromise() | ||
expect(resp).to.be.eql(null) | ||
expect(err).to.be.an.instanceof(CustomError) | ||
expect(err.message).to.be.eql('Error Rejected') | ||
}) | ||
|
||
it('should fail when the decorator is used in a non-async function', () => { | ||
expect(() => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
execute() { | ||
return true | ||
} | ||
} | ||
const buzz = new Buzz() | ||
buzz.execute() | ||
}).to.throw('You might decorate an async function with use @AsyncInlineError') | ||
}) | ||
|
||
it('should fail when the decorated method throws an error', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
throwAnException() { | ||
throw new Error('Error exception') | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
expect(() => { | ||
buzz.throwAnException() | ||
}).to.throw('You might decorate an async function with use @AsyncInlineError') | ||
}) | ||
}) |
92 changes: 92 additions & 0 deletions
92
packages/sui-decorators/test/server/asyncInlineErrorSpec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import {expect} from 'chai' | ||
|
||
import {AsyncInlineError} from '../../src/index.js' | ||
|
||
describe('AsyncInlineError decorator', () => { | ||
it('should exist', () => { | ||
expect(AsyncInlineError).to.exist | ||
expect(AsyncInlineError).to.be.a('function') | ||
}) | ||
|
||
it('should return an array [null, resp] when the promise is resolved', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnASuccessPromise() { | ||
return Promise.resolve(true) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
expect(await buzz.returnASuccessPromise()).to.be.eql([null, true]) | ||
}) | ||
|
||
it('should return an array [Error, null] when the promise is rejected', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnAFailedPromise() { | ||
return Promise.reject(new Error('Error Rejected')) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
const [err, resp] = await buzz.returnAFailedPromise() | ||
expect(resp).to.be.eql(null) | ||
expect(err).to.be.an.instanceof(Error) | ||
expect(err.message).to.be.eql('Error Rejected') | ||
}) | ||
|
||
it('should preserve the context', async () => { | ||
class Buzz { | ||
name = 'Carlos' | ||
|
||
@AsyncInlineError() | ||
returnASuccessPromise() { | ||
return Promise.resolve(this.name) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
expect(await buzz.returnASuccessPromise()).to.be.eql([null, 'Carlos']) | ||
}) | ||
|
||
it('should works with an Error subclass', async () => { | ||
class CustomError extends Error {} | ||
class Buzz { | ||
@AsyncInlineError() | ||
returnAFailedPromise() { | ||
return Promise.reject(new CustomError('Error Rejected')) | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
const [err, resp] = await buzz.returnAFailedPromise() | ||
expect(resp).to.be.eql(null) | ||
expect(err).to.be.an.instanceof(CustomError) | ||
expect(err.message).to.be.eql('Error Rejected') | ||
}) | ||
|
||
it('should fail when the decorator is used in a non-async function', () => { | ||
expect(() => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
execute() { | ||
return true | ||
} | ||
} | ||
const buzz = new Buzz() | ||
buzz.execute() | ||
}).to.throw('You might decorate an async function with use @AsyncInlineError') | ||
}) | ||
|
||
it('should fail when the decorated method throws an error', async () => { | ||
class Buzz { | ||
@AsyncInlineError() | ||
throwAnException() { | ||
throw new Error('Error exception') | ||
} | ||
} | ||
const buzz = new Buzz() | ||
|
||
expect(() => { | ||
buzz.throwAnException() | ||
}).to.throw('You might decorate an async function with use @AsyncInlineError') | ||
}) | ||
}) |