Proxy async methods to await prerequisite EventEmitter
events:
import eventDependentPromises from '@peak-ai/event-dependent-promises';
import sdk, { Data, Events } from 'some-third-party-eventemitter';
import { APIGatewayEvent } from 'aws-lambda';
const dependentSdk = eventDependentPromises(
sdk,
Events.READY,
Events.INIT_TIMEOUT,
{
getData(key: string) {
// sdk.getData returns Promise<Data>
return sdk.getData(key);
},
},
);
export const handler = (event: APIGatewayEvent): Promise<Data> => {
const { body: key } = event;
/* dependentSdk.getData(key) will invoke
* and return sdk.getData(key) once the READY
* event has been emitted or immediately
* if it's been fired already. */
return dependentSdk.getData(key);
};
If we wanted to, we could subscribe to our EventEmitter
directly:
import sdk, { Data, Events } from 'some-third-party-eventemitter';
import { APIGatewayEvent, Context, Callback } from 'aws-lambda';
import keyService from './key-service';
let hasSdkInitialised = false;
export const handler = async (event: APIGatewayEvent, context: Context, cb: Callback<Data>): void => {
const { body: id } = event;
const key = await keyService.getForId(id);
if (hasSdkInitialised) {
const data = await sdk.getData(key);
cb(null, data);
return;
}
sdk.once(Events.READY, async () => {
hasSdkInitialised = true;
const data = await sdk.getData(key);
cb(null, data);
});
};
There are some key drawbacks, however, that present themselves:
- Mixing multiple async paradigms (
Promise
s and callbacks!) - Having to manually track whether the
EventEmitter
(sdk
) has already fired its initialisation event (required for warm lambda invocations)
Event-Dependent Promises is directed at resolving (excuse the pun) these two issues by:
- internally tracking when the prerequisite event has been emitted
- proxying a set of methods to return
Promise
s, which won't resolve until the prerequisite event has been fired once
Here's the previous example when using Event-Dependent Promises:
import eventDependentPromises from '@peak-ai/event-dependent-promises';
import sdk, { Data, Events } from 'some-third-party-eventemitter';
import { APIGatewayEvent } from 'aws-lambda';
import keyService from './key-service';
const dependentSdk = eventDependentPromises(
sdk,
Events.READY,
Events.INIT_TIMEOUT,
{
getData(key: string) {
// sdk.getData returns Promise<Data>
return sdk.getData(key);
},
},
);
export const handler = async (event: APIGatewayEvent): Promise<Data> => {
const { body: id } = event;
const key = await keyService.getForId(id);
/* Remember, dependentSdk.getData(key)
* will wait for Events.READY to have
* been emitted once before calling
* and returning sdk.getData(key) */
return dependentSdk.getData(key);
};
- Internally tracks whether the required event has already been fired, in which case execution will continue; contrast this with
events.once()
, which will never resolve if the event has already occurred - Supports a custom rejection event (over simply rejecting on
'error'
being emitted) - Works with Node.js 10
You can install Event-Dependent Promises from npm:
npm i -E @peak-ai/event-dependent-promises
The library comprises of a single function, exposed via a default
binding:
import eventDependentPromises from '@peak-ai/event-dependent-promises';
If you're using CommonJS, this means you'll have to destructure and rename the default
binding:
const { default: eventDependentPromises } = require('@peak-ai/event-dependent-promises');
function eventDependentPromises<TSource extends EventEmitter, TMethods extends Methods>(
eventSource: TSource,
successEvent: string,
failureEvent: string,
methods: TMethods,
): TMethods
eventSource
: anyEventEmitter
successEvent
: the name of the event that suggests successful initialisationfailureEvent
: the name of the event that suggests unsuccessful initialisation. If this is emitted, then thePromise
s returned by any of themethods
will rejectmethods
: an object whose properties refer to async methods (i.e. returns aPromise
or usesasync/await
) e.g:
const methods = {
fetchItems() {
return window.fetch('/items');
},
};
TMethods
This is, on the surface, the same object you passed for the methods
argument. However, each method is augmented to await the required event emission if it hasn't already occured.
Prerequisites:
- Fork this repo
git clone <your fork>
cd event-dependent-promises
nvm i
yarn
You can then run:
yarn lint
: runs ESLint against the source codeyarn format
: fixes and overwrites any source files that don't adhere to our Prettier configyarn format:check
: checks the formatting of our source files and fails if any don't adhere to said config, for CI and prepublish purposesyarn build
: runs the TypeScript compiler against the project and produces distributable outputyarn test
: runs the unit testsyarn test:dist
: runs the compiled unit tests against the compiled source. Typically used by our pre-commit hook, CI, and pre-publish script
Despite being primarily maintained by Peak, we welcome and appreciate any contributions from the community! Please see the contribution guidelines for more info.