Skip to content

Commit

Permalink
Version 1.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
nylen committed Apr 15, 2019
1 parent f566613 commit a3ee9cb
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 7 deletions.
37 changes: 31 additions & 6 deletions README.md
Expand Up @@ -52,10 +52,35 @@ In large apps, it is a good idea to enforce separation of different types of
hooks by either prefixing `identifier` hook names with `namespace.identifier`,
or (even better) using separate `hooks` objects for each part of the app.

All filters and actions are **synchronous**. If you need asynchronous
behavior, you will need to use a filter, return a `Promise` **synchronously**
from each filter callback, and wait for the final `Promise` to be fulfilled in
your app.
## Sync or Async?

A future **major version** of this library may include first-class support for
`Promise`s and `async`/`await`. PRs towards this goal are welcome.
All filters and actions are **synchronous** by default, and this library
contains no special code for async callbacks or promises.

However, because `async` functions just return `Promise` objects under the
covers, you can easily pass a `Promise` through a chain of hooks. If you
follow a couple of simple rules, then the final result from `applyFilters` will
be a promise that resolves or fails when all of its attached functions have
finished processing.

For hooks that may require asynchronous behavior, the app needs to start the
filter chain with a `Promise` object:

```js
const finalValue = await applyFilters(
'my_async_code_path',
'defaultValue'
);
```

Then, individual filters can be written as `async` functions, as long as they
accept a promise as an argument and `await` it at some point:

```js
addFilter( 'my_async_code_path', async p => {
const value = await p;
return value + '/modified';
} );
```

See `index.test.js` for more examples.
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "@nylen/wp-hooks",
"description": "WordPress actions and filters in JS",
"version": "1.0.0",
"version": "1.0.2",
"repository": {
"type": "git",
"url": "https://github.com/nylen/node-wp-hooks"
Expand Down
112 changes: 112 additions & 0 deletions test/index.test.js
Expand Up @@ -90,6 +90,118 @@ test( 'run a filter with no callbacks', () => {
expect( applyFilters( 'test.filter', 42 ) ).toEqual( 42 );
} );

test( 'run a filter with promises', async () => {
const log = [];

addFilter( 'promise', p => {
log.push( 'hook1 wait' );
return p.then( value0 => {
log.push( [ 'hook1 sees', value0 ] );
return new Promise( resolve => {
const value1 = [ value0, 'hook1' ];
setTimeout( () => {
log.push( [ 'hook1 resolve', value1 ] );
resolve( value1 );
}, 2 );
} );
} );
} );

addFilter( 'promise', p => {
log.push( 'hook2 wait' );
return p.then( value1 => {
log.push( [ 'hook2 sees', value1 ] );
return new Promise( resolve => {
const value2 = [ value1, 'hook2' ];
setTimeout( () => {
log.push( [ 'hook2 resolve', value2 ] );
resolve( value2 );
}, 8 );
} );
} );
} );

const value = await applyFilters( 'promise', new Promise( resolve => {
setTimeout( () => {
log.push( 'original fulfill' );
resolve( '0riginalvalue' );
}, 5 );
} ) );

expect( value ).toEqual( [ [ '0riginalvalue', 'hook1' ], 'hook2' ] );
expect( log ).toEqual( [
"hook1 wait",
"hook2 wait",
"original fulfill",
[ "hook1 sees", "0riginalvalue" ],
[ "hook1 resolve", [ "0riginalvalue", "hook1" ] ],
[ "hook2 sees", [ "0riginalvalue", "hook1" ] ],
[ "hook2 resolve", [ [ "0riginalvalue", "hook1" ], "hook2" ] ],
] );
} );

test( 'run a filter with await/async (readme example)', async () => {
addFilter( 'my_async_code_path', async p => {
// This may or may not be a Promise, depending on whether a previous
// filter has registered an async operation or whether the original
// call was passed a Promise. It works fine either way, but all hooks
// that may be part of a chain involving async computations need to
// adhere to this convention.
const value = await p;
return value + '/modified';
} );

const finalValue = await applyFilters(
'my_async_code_path',
'defaultValue'
// This could just as easily be:
// new Promise( resolve => resolve( 'defaultValue' ) )
// but `await` on a non-Promise value works fine too.
);

expect( finalValue ).toEqual( 'defaultValue/modified' );
} );

test( 'run a filter with await/async (full example)', async () => {
const log = [];

addFilter( 'promise', async p => {
log.push( 'hook1 wait' );
const value0 = await p;
log.push( [ 'hook1 sees', value0 ] );
const value1 = [ value0, 'hook1' ];
log.push( [ 'hook1 resolve', value1 ] );
return value1;
} );

addFilter( 'promise', async p => {
log.push( 'hook2 wait' );
const value1 = await p;
log.push( [ 'hook2 sees', value1 ] );
const value2 = [ value1, 'hook2' ];
log.push( [ 'hook2 resolve', value2 ] );
return value2;
} );

const value = await applyFilters( 'promise', new Promise( resolve => {
setTimeout( () => {
log.push( 'original fulfill' );
resolve( '0riginalvalue' );
} );
} ) );

expect( value ).toEqual( [ [ '0riginalvalue', 'hook1' ], 'hook2' ] );
expect( log ).toEqual( [
"hook1 wait",
"hook2 wait",
"original fulfill",
[ "hook1 sees", "0riginalvalue" ],
[ "hook1 resolve", [ "0riginalvalue", "hook1" ] ],
[ "hook2 sees", [ "0riginalvalue", "hook1" ] ],
[ "hook2 resolve", [ [ "0riginalvalue", "hook1" ], "hook2" ] ],
] );
} );

test( 'add and remove a filter', () => {
addFilter( 'test.filter', filter_a );
expect( removeAllFilters( 'test.filter' ) ).toEqual( 1 );
Expand Down

0 comments on commit a3ee9cb

Please sign in to comment.