Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Jul 29, 2018
1 parent 90a9f94 commit 10a5b8d
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 6 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,40 @@
[![Greenkeeper badge](https://badges.greenkeeper.io/overlookmotel/co-simple.svg)](https://greenkeeper.io/)
[![Coverage Status](https://img.shields.io/coveralls/overlookmotel/co-simple/master.svg)](https://coveralls.io/r/overlookmotel/co-simple)

## What's it for?

Very simple coroutine wrapper. No cruft, no dependencies, no funny business!

Compatible replacement for `async/await` for versions of Node which don't support async/await natively.

All the other implementations I could find on NPM either include a load of extraneous features, or (unbelievably) are buggy. [co](https://www.npmjs.com/package/co) is great, but includes features which async/await doesn't.

This implementation is identical to the code [babel](https://babeljs.io/) produces when transpiling `async/await`.

So, if you're on a version of Node which doesn't support async/await, you can use this now, and then safely replace `co(function*() {})` with `async function() {}` once you update to a version which supports async/await natively.

## Usage

```js
const co = require('co-simple');

const foo = co( function*(a, b, c) {
const res = yield Promise.resolve(a * b * c);
return res;
} );
```

...is identical to...

```js
const foo = async function(a, b, c) {
const res = await Promise.resolve(a * b * c);
return res;
};
```

Requires `Promise` to be available in global scope, and support for generator functions (Node 4+).

## Tests

Use `npm test` to run the tests. Use `npm run cover` to check coverage.
Expand Down
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Changelog

## Next

* Initial release
27 changes: 26 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,30 @@
'use strict';

// Exports
module.exports = function() {
module.exports = fn => {
return function() {
const gen = fn.apply(this, arguments);

return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (err) {
return reject(err);
}

if (info.done) return resolve(value);

return Promise.resolve(value).then(value => {
step('next', value);
}, err => {
step('throw', err);
});
}

step('next');
});
};
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"bugs": {
"url": "https://github.com/overlookmotel/co-simple/issues"
},
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.2",
"cross-env": "^5.2.0",
"istanbul": "^0.4.5",
Expand Down
264 changes: 261 additions & 3 deletions test/all.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,276 @@
// Modules
const chai = require('chai'),
{expect} = chai,
chaiAsPromised = require('chai-as-promised'),
co = require('../lib/');

// Init
chai.config.includeStack = true;
chai.use(chaiAsPromised);

// Tests

/* jshint expr: true */
/* global describe, it */

describe('Tests', function() {
it.skip('all', function() {
expect(co).to.be.ok;
describe('co', function() {
it('returns a function', function() {
const fn = co(function*() {}); // jshint ignore:line
expect(fn).to.be.a('function');
});

it('passes arguments to generator function', function() {
const arg1 = {a: 1}, arg2 = {b: 2};
let calledArgs;
const fn = co(function*() {
calledArgs = Array.prototype.slice.call(arguments);
}); // jshint ignore:line
const p = fn(arg1, arg2);
expect(calledArgs).to.have.length(2);
expect(calledArgs[0]).to.equal(arg1);
expect(calledArgs[1]).to.equal(arg2);
return p;
});

it('passes context to generator function', function() {
const ctx = {c: 3};
let calledCtx;
const fn = co(function*() {
calledCtx = this;
}); // jshint ignore:line
const p = fn.call(ctx);
expect(calledCtx).to.equal(ctx);
return p;
});
});

describe('returned function', function() {
it('calls generator function synchronously', function() {
let called = false;
const fn = co(function*() {
called = true;
}); // jshint ignore:line
const p = fn();
expect(called).to.be.true;
return p;
});

it('returns a promise', function() {
const fn = co(function*() {}); // jshint ignore:line
const p = fn();
expect(p).to.be.instanceof(Promise);
return p;
});

describe('returns promise resolving to plain return value of generator function', function() {
it('with no yield statements', function() {
const ret = {a: 123};
const fn = co(function*() {
return ret;
}); // jshint ignore:line
const p = fn();
return expect(p).to.eventually.equal(ret);
});

it('with 1 yield statement', function() {
const ret = {a: 123};
const fn = co(function*() {
yield Promise.resolve(1);
return ret;
});
const p = fn();
return expect(p).to.eventually.equal(ret);
});

it('with 2 yield statements', function() {
const ret = {a: 123};
const fn = co(function*() {
yield Promise.resolve(1);
yield Promise.resolve(1);
return ret;
});
const p = fn();
return expect(p).to.eventually.equal(ret);
});
});

describe('returns promise resolving to resolved value of promise returned from generator function', function() {
it('with no yield statements', function() {
const ret = {a: 123};
const fn = co(function*() {
return Promise.resolve(ret);
}); // jshint ignore:line
const p = fn();
return expect(p).to.eventually.equal(ret);
});

it('with 1 yield statement', function() {
const ret = {a: 123};
const fn = co(function*() {
yield Promise.resolve(1);
return Promise.resolve(ret);
});
const p = fn();
return expect(p).to.eventually.equal(ret);
});

it('with 2 yield statements', function() {
const ret = {a: 123};
const fn = co(function*() {
yield Promise.resolve(1);
yield Promise.resolve(1);
return Promise.resolve(ret);
});
const p = fn();
return expect(p).to.eventually.equal(ret);
});
});

describe('rejects promise if error thrown in generator function', function() {
it('before any yield statements', function() {
const err = new Error('boo!');
const fn = co(function*() {
throw err;
}); // jshint ignore:line
const p = fn();
return expect(p).to.be.rejectedWith(err);
});

it('after 1 yield statement', function() {
const err = new Error('boo!');
const fn = co(function*() {
yield Promise.resolve(1);
throw err;
});
const p = fn();
return expect(p).to.be.rejectedWith(err);
});

it('after 2 yield statements', function() {
const err = new Error('boo!');
const fn = co(function*() {
yield Promise.resolve(1);
yield Promise.resolve(1);
throw err;
});
const p = fn();
return expect(p).to.be.rejectedWith(err);
});
});

describe('awaits on yield', function() {
it('when yield promise', function() {
let called = false;
const fn = co(function*() {
yield Promise.resolve(1);
called = true;
});
const p = fn();
expect(called).to.be.false;
return p.then(() => {
expect(called).to.be.true;
});
});

it('when yield plain value', function() {
let called = false;
const fn = co(function*() {
yield 123;
called = true;
});
const p = fn();
expect(called).to.be.false;
return p.then(() => {
expect(called).to.be.true;
});
});
});

describe('yield returns value', function() {
it('when yield promise', function() {
const val = {d: 4};
let yielded;
const fn = co(function*() {
yielded = yield Promise.resolve(val);
});
const p = fn();
return p.then(() => {
expect(yielded).to.equal(val);
});
});

it('when yield plain value', function() {
const val = {d: 4};
let yielded;
const fn = co(function*() {
yielded = yield val;
});
const p = fn();
return p.then(() => {
expect(yielded).to.equal(val);
});
});
});

describe('yield throws when promise rejects', function() {
it('rejects promise with error from yielded promise', function() {
const err = new Error('boo!');
const fn = co(function*() {
yield Promise.reject(err);
});
const p = fn();
return expect(p).to.be.rejectedWith(err);
});

it('aborts execution of generator function', function() {
const err = new Error('boo!');
let called = false;
const fn = co(function*() {
yield Promise.reject(err);
called = true;
});
const p = fn();
return p.then(
() => {throw new Error('Should not resolve');},
() => {
expect(called).to.be.false;
}
);
});

it('catch block called', function() {
const err = new Error('boo!');
let caught;
const fn = co(function*() {
try {
yield Promise.reject(err);
} catch (_err) {
caught = err;
}
});
const p = fn();
return p.then(() => {
expect(caught).to.equal(err);
});
});

it('finally block called', function() {
const err = new Error('boo!');
let called = false;
const fn = co(function*() {
try {
yield Promise.reject(err);
} finally {
called = true;
}
});
const p = fn();
return p.then(
() => {throw new Error('Should not resolve');},
() => {
expect(called).to.be.true;
}
);
});
});
});

0 comments on commit 10a5b8d

Please sign in to comment.