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

runner-js

v0.0.3

Published

Better Async Handler To Simplify Your Async Flow Process And Code Testing

Downloads

5

Readme

Runner Js

Better Async Handler To Simplify Your Async Flow Process And Code Testing. It's inspired by redux-saga but it is just a generator function runner.

Installation

You can import runner.js then process it with your preprocessor.

You can install it via NPM

npm install runner-js

Dependencies

You need to install babel-polyfill and babel regenerator plugin and put it in the first line of your main entry file to make it works. You can check the example here.

npm install babel-polyfill babel-plugin-transform-regenerator

And Don't forget to add the plugin to your .babelrc

{
  "plugins": ["transform-regenerator"]
}

Why I Need This?

Probably you don't need it. But in some cases you'll find a busy async process that you'll hard to organize with ordinary Promise function. For example:

import api from '../api'

// Variable for saving the responses
let product, seller, statistic;

api.fetchProduct()
.then((res) => {
  product = res
  return api.fetchSeller(product.id)
})
.then((res) => {
  seller = res
  return api.statistic(product, seller)
})
.then((res) => {
  statistic = res
  return api.needStatisticProductAndSeller(statistic, product, seller)
})

Or you can skip the let declaration

// source: https://codepen.io/aurelien-bottazini/pen/VPQLBp?editors=0011

const api = {
  fetchProduct() { return Promise.resolve({ id: 'productId'}) },
  fetchSeller(id) { return Promise.resolve('seller') },
  statistic(product, seller) { return  Promise.resolve('stats') },
  needStatisticProductAndSeller(statistic, product, seller) {
    return Promise.resolve('finalResult')
  },
};

api.fetchProduct()
  .then((product) => api.fetchSeller(product.id)
  .then((seller) => ({ product, seller })))
  .then(({ product, seller }) => api.statistic(product, seller)
  .then((statistic) => ({ product, seller, statistic })))
  .then(({ product, seller, statistic }) =>  api.needStatisticProductAndSeller(statistic, product, seller))
  .then(console.log);

The solution is pretty simple, You can use async/await.

// source: https://forum.vuejs.org/t/let-s-write-better-vuex-action-with-runner/5527/2

import api from '../api'

async function do () {
  const product = await api.fetchProduct()
  const seller = await api.fetchSeller(product.id)
  const statistic = await api.statistic(product, seller)
  const res = await api.needStatisticProductAndSeller(statistic , product, seller)
  // ...
}

You could use aync/await which are compatible with Promises. You can easily do that with Babel or natively in Chrome and Opera. Firefox and Edge support is coming in their next versions (FX 52, Edge 15). But another point that you should notice is "How can you test it Effortlessly?". For now, I have no idea to test async/await function. But, with runner js and Generator function you don't need to run the real request or function in the test section. You just need make a simple expectation effortlessly.

How about Runner JS?

According to our cases above, we can simplify that code with Generator Function. It will make our async code looks like synchronous code. Take a look:

import Runner, { call } from 'runner-js'
import api from '../api'

function *fetchFlow() {
  let product = yield call(api.fetchProduct)
  let seller = yield call(api.fetchSeller, { product })
  let statistic = yield call(api.statistic, { product, seller })
  let lastFetch = yield call(api.needStatisticProductAndSeller, { statistic, product, seller })
  return lastFetch
}

Runner(fetchFlow)

Pretty simple right? It works like async/await function. But You'll get a better testing process although your testing a deep promise function. Take a peek:

import { call } from 'runner-js'
import api from '../api'
import { fetchFlow } from '../actions';
import assert from 'assert';

describe('fetchFlow()', function () {

  it('Should Run The Flow Correctly ', function () {
    let process = fetchFlow()

    let fakeRespon = {}

    assert.deepEqual(process.next().value, call(api.fetchProduct))
    assert.deepEqual(process.next(fakeRespon).value, call(api.fetchSeller, { fakeRespon }))
    assert.deepEqual(process.next(fakeRespon).value, call(api.statistic, { fakeRespon }))
    assert.deepEqual(process.next(fakeRespon).value, call(api.needStatisticProductAndSeller, { fakeRespon }))
  });

});

Wait? Are you sure it's a valid testing process? I'm not sure yet. But It works. You don't need to mock the promises, You don't need run the real fetch function in the browser, It just works. Let me tell you how call() function works.

call() function is just an ordinary function that return a plain object contains our real function, I call it wrapper. So, the generator only pass the plain object while the runner excute the function from the object. Since we don't use the runner, we can test our code like the example above, Just need to deep compare two object.

How About Nested Generator Function?

It's just the same, you can wrap it with call() function.

import Runner, { call, delay } from 'runner-js'
import api from '../api'

function *nestedGenFunc() {
  yield call(delay, 1000)
  return 1000
}

function *fetchFlow() {
  let nested = yield call(nestedGenFunc)
}

Runner(fetchFlow)

Is it take care some parallel async process?

Yes, it should. Just wrap it within an array! Check it out.

import Runner, { call } from 'runner-js'
import api from '../api'

function *fetchFlow() {
  let responses = yield [
    call(api.fetchProduct),
    call(api.otherApis)
  ]
  responses[0] // it will always the fetchProduct response
  responses[1] // it will always the otherApis response
}

Runner(fetchFlow)

Or you can identity your call with making a parallel call within an object.

import Runner, { call } from 'runner-js'
import api from '../api'

function *fetchFlow() {
  let allRes = yield {
    product: call(api.fetchProduct),
    other: call(api.otherApis)
  }
  const productRes = allRes.product
  const otherRes = allRes.other
}

Runner(fetchFlow)

It cares with concurrentcy

Parallel call will make the every response has same index with the each its call wrapper. While the concurrent wrapper will push responses which recieved faster.

import Runner, { call, concurrent } from 'runner-js'
import api from '../api'

function *fetchFlow() {
  let responses = yield concurrent([
    call(api.fetchProduct),
    call(api.otherApis)
  ])
  responses[0] // it can be fetchProduct response or otherApis response. Depend on which is faster
}

Runner(fetchFlow)

Need some race?

Runner has a race wrapper to make a race between an async progress. It only store one response which is fastest.

import Runner, { call, race } from 'runner-js'
import api from '../api'

function *fetchFlow() {
  let responses = yield race({
    product: call(api.fetchProduct),
    other: call(api.otherApis)
  })
  if (responses.product) {
    console.log('the winner is fetchProduct');
  } else {
    console.log("The winner is otherApis");
  }
}

Runner(fetchFlow)

So now, you can test the flow and the fetch process separately. It will make your code easy to test. No more reason to not doing a test.

API

| Method | Format | Description | | :--- | :--- | :--- | | call() | call(func, [argument1, [argument2, [argument3, ...]]]) | It's used to call some function. For best practice you should have your function to be a promise. And the rest arguments is the arguments that will be passed to the function. | | delay() | delay(number) | It's just a simple method to delay some function inside the saga. Maybe, It will not used cause I made it just for making a fake async proccess | | concurrent() | concurrent(Array of call Function) | It's used to make a concurrentcy async function. it only take the first argument which is an Array of call() function | | race() | race(Object of call Function) | It's used to make a race between some async function. it only take the first argument which is an Object of call() function |

Credits

Thank You for Making this useful~

Let's talk about some projects with me

Just Contact Me At:

License

MIT Copyright (c) Naufal Rabbani