npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

cosed

v1.1.9

Published

co side effects as data

Downloads

6

Readme

cosed Build Status

Fork of co that describes asynchronous tasks as data.

Why?

Instead of a generator activating side-effects it instead yields data objects that represent how side-effects ought to be executed. This pushes side-effects to co instead of the application itself.

Effectively this makes testing side-effects as easy as checking that each step in a generator returns the proper data structure.

This library was inspired by redux-saga and re-frame. Whenever I left the world of redux-saga and wanted to test my async/await/generator functions it would require mocking/intercepting HTTP requests which is a terrible developer experience after coming from describing side-effects as data.

Effects as Data talk by Richard Feldman

How?

cosed will work exactly like co with the exception that it can handle a new yielded value type: effect objects. An effect object looks something like this:

{
  "type": "CALL",
  "fn": [function],
  "args": ["list", "of", "arguments"]
}

task is an alias for the co function.

import { call, task } from 'cosed';

function* fetchBin() {
  const resp = yield call(fetch, 'http://httpbin.org/get');
  // sending an array makes `call` activate the function `json` on `resp` object
  // this is required because of the way fetch uses context to determine if the Body
  // promise has been used already.
  const data = yield call([resp, 'json']);
  return { ...data, extra: 'stuff' };
}

task(fetchBin)
  .then(console.log)
  .catch(console.error);

Check out the API section for more effects.

Testing

Taking the previous example, this is how you would test it:

const test = require('tape');

test('test fetchBin', (t) => {
  const gen = fetchBin();

  t.deepEqual(
    gen.next().value,
    call(fetch, 'http://httpbin.org/get'),
    'should make http request',
  );

  const respValue = { resp: 'value', json: 'hi' };
  t.deepEqual(
    gen.next(respValue).value,
    call([respValue, 'json']),
    'should get json from response',
  );

  const last = gen.next({ data: 'value' });
  t.ok(last.done, 'generator should finish');
  t.deepEqual(
    last.value,
    { data: 'value', extra: 'stuff' },
    'should return data',
  );
});

Using a little helper library called gen-tester we can make this even easier.

const { genTester, yields } = require('gen-tester');

test('test fetchBin', (t) => {
  t.plan(1);

  const respValue = { resp: 'value', json: 'hi' };
  const returnValue = { data: 'value', extra: 'stuff' };

  const tester = genTester(genCall);
  const { actual, expect } = tester(
    yields(
      call(fetch, 'http://httpbin.org/get'),
      respValue, // the result value of `resp` in the generator
    ),
    yields(
      call([respValue, 'json']),
      { data: 'value' }, // the result value of `data` in the generator
    ),
    returnValue,
  );

  t.deepEqual(actual, expected);
});

Take a close look here. When the generator function does not get called by task all it does is return JSON at every yield. This is the brilliance of describing side-effects as data: we can test our generator function synchronously, without needing any HTTP interceptors or mocking functions! So even though at every yield this library will make asynchronous calls, for testing, we can step through the generator one step after another and make sure the yield makes the correct call.

API

task

Manages async flow for a generator. This is an alias to the co function.

call

const { task, call } = require('cosed');
const fetch = require('node-fetch');

function* example() {
  yield call(fetch, 'http://google.com')
  return 'hi';
}

task(example);

all

Uses Promise.all to execute effects in parallel. Could be an array of effects or an object of effects.

const { task, call, all } = require('cosed');
const fetch = require('node-fetch');

function* example() {
  const resp = yield all([
    call(fetch, 'http://google.com'),
    call(fetch, 'http://something-else.com'),
  ]);
  const data = yield all(resp.map((r) => call([r, 'json'])));
  return data;
}

task(example);

spawn

Spawns an effect without the generator waiting for that effect to finish.

const { task, spawn } = require('cosed');
const fetch = require('node-fetch');

function effect() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
      console.log('ACTIVATE');
    }, 5000);
  });
}

function* example() {
  yield spawn(effect);
  console.log('COOL');
}

task(example);
// COOL
// ... five seconds later
// ACTIVATE

delay

This will sleep the generator for the designated amount of time.

const { task, delay } = require('cosed');

function* example() {
  console.log('INIT')
  yield delay(1000);
  console.log('END');
}

task(example);
// INIT
// ... one second later
// END

factory

This is what creates task. This allows end-developers to build their own effect middleware. When using middleware it must return a promise, something that co understands how to handle, and to allow other middleware to handle the effect as well, you must return next(effect);

const { factory } = require('cosed');

const ERROR = 'ERROR';
const error = (msg) => ({ type: ERROR, msg });
const middleware = (next) => (effect) => {
  if (effect.type === ERROR) {
    return Promise.reject(effect.msg);
  }

  return next(effect);
};

function* example() {
  yield error('SOMETHING HAPPENED');
}

const customTask = factory(middleware);
customTask(example).catch((err) => {
  console.log(`ERROR: ${err}`);
});

// ERROR: SOMETHING HAPPENED