Skip to content

Commit

Permalink
feat: add async
Browse files Browse the repository at this point in the history
Closes #830
  • Loading branch information
char0n committed Feb 15, 2019
1 parent 217dc12 commit cbd709f
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { curryN, bind } from 'ramda';
import curry1 from 'ramda/src/internal/_curry1';

import resolveP from './resolveP';
import rejectP from './rejectP';

/**
* Takes a generator function and returns an async function.
* The async function returned is a curried function whose arity matches that of the generator function.
*
* Note: This function is handy for environments that does support generators but doesn't support async/await.
*
* @func async
* @memberOf RA
* @since {@link https://char0n.github.io/ramda-adjunct/2.16.0|v2.16.0}
* @category Function
* @sig Promise c => (a, b, ...) -> a -> b -> ... -> c
* @param {Function} generatorFn The generator function
* @return {Function} Curried async function
* @see {@link https://www.promisejs.org/generators/}
* @example
*
* const asyncFn = RA.async(function* generator(val1, val2) {
* const a = yield Promise.resolve(val1);
* const b = yield Promise.resolve(val2);
*
* return a + b;
* });
*
* asyncFn(1, 2); //=> Promise(3)
*
*/
const async = curry1(generatorFn => {
function asyncWrapper(...args) {
const iterator = bind(generatorFn, this)(...args);

const handle = result => {
const resolved = resolveP(result.value);

return result.done
? resolved
: resolved.then(
value => handle(iterator.next(value)),
error => handle(iterator.throw(error))
);
};

try {
return handle(iterator.next());
} catch (error) {
return rejectP(error);
}
}

if (generatorFn.length > 0) {
return curryN(generatorFn.length, asyncWrapper);
}
return asyncWrapper;
});

export default async;
10 changes: 10 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GeneratorFn } from 'jsverify';

declare var RA: RamdaAdjunct.Static;

declare namespace RamdaAdjunct {
Expand Down Expand Up @@ -1085,6 +1087,14 @@ declare namespace RamdaAdjunct {
*/
sign(val: number): number;

/**
* Takes a generator function and returns an async function.
* The async function returned is a curried function whose arity matches that of the generator function.
*
* Note: This function is handy for environments that does support generators but doesn't support async/await.
*/
async(generatorFn: Function): Function;

/**
* Identity type.
*/
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export { default as Y } from './Y';
export { default as seq } from './seq';
export { default as sequencing } from './seq';
export { default as dispatch } from './dispatch';
export { default as async } from './async';
// List
export { default as mapIndexed } from './mapIndexed';
export { default as reduceIndexed } from './reduceIndexed';
Expand Down
146 changes: 146 additions & 0 deletions test/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import * as R from 'ramda';
import { assert } from 'chai';

import eq from './shared/eq';
import * as RA from '../src';

describe('async', function() {
context('given wrapping of generator', function() {
specify('should mimic async/await behavior', async function() {
const asyncFn = RA.async(function* generator(val1, val2) {
const a = yield RA.resolveP(val1);
const b = yield RA.resolveP(val2);

return a + b;
});
const expected = await asyncFn(1, 2);

eq(expected, 3);
});

context('and the generator throw Error', function() {
specify('should resolve with rejection', async function() {
const asyncFn = RA.async(function* generator(val1, val2) {
yield RA.resolveP(val1);
yield RA.resolveP(val2);

throw new Error('generator error');
});

try {
await asyncFn(1, 2);
throw new Error('fulfilling should fail');
} catch (error) {
assert.instanceOf(error, Error);
eq(error.message, 'generator error');
}
});
});
});

it('should support yield delegation', async function() {
const foo = function* generator(val1, val2) {
const a = yield RA.resolveP(val1);
const b = yield RA.resolveP(val2);

return a + b;
};
const bar = RA.async(function* generator(val1, val2) {
const a = yield RA.resolveP(val1);
const b = yield RA.resolveP(val2);
const c = yield* foo(a, b);

return c + 3;
});

eq(await bar(1, 2), 6);
});

it('should support async delegation', async function() {
const foo = RA.async(function* generator(val1, val2) {
const a = yield RA.resolveP(val1);
const b = yield RA.resolveP(val2);

return a + b;
});
const bar = RA.async(function* generator(val1, val2) {
const a = yield RA.resolveP(val1);
const b = yield RA.resolveP(val2);
const c = yield foo(a, b);

return c + 3;
});

eq(await bar(1, 2), 6);
});

it('should support recursion delegation', async function() {
const async = RA.async(function* generator(val) {
let newVal = val;

if (val > 1) {
newVal = yield async(val - 1);
}

return yield newVal;
});

eq(await async(10), 1);
});

it('should curry', async function() {
const async = RA.async(R.__);
const asyncFn = async(function* generator() {
yield RA.resolveP(1);
return 2;
});
const expected = await asyncFn();

eq(expected, 2);
});

context('given wrapping of generator with arity of 2', function() {
let asyncFn;

beforeEach(function() {
// eslint-disable-next-line require-yield
asyncFn = RA.async(function* generator(a, b) {
return a + b;
});
});

specify('should translate generator arity to wrapper', function() {
eq(asyncFn.length, 2);
});

specify('should curry wrapper to appropriate arity', async function() {
eq(await asyncFn(1, 2), 3);
eq(await asyncFn(1)(2), 3);
});
});

context('given wrapping of generator with arity of 0', function() {
context('then the resulting wrapper', function() {
let asyncFn;

beforeEach(function() {
// eslint-disable-next-line require-yield
asyncFn = RA.async(function* generator() {
return 1;
});
});

specify('should not support placeholder', async function() {
const expected = await asyncFn(R.__);

eq(expected, 1);
});

specify('should support call without arguments', async function() {
const expected = await asyncFn();

eq(expected, 1);
});
});
});
});

0 comments on commit cbd709f

Please sign in to comment.