• Stars
    star
    142
  • Rank 258,495 (Top 6 %)
  • Language
    JavaScript
  • Created about 9 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

πŸ’‘ Powerful control flow using ES6 generators

watt

npm version Build Status Dependency Status

Powerful control flow using generators

watt lets you write your async Javascript as if it were synchronous, which results in much simpler, more readable code.

ES6 introduced generators, which are functions that can be paused and resumed using the yield keyword. This lets us do some cool things, for instance letting a library handle control flow.

Features:

  • Eliminates "callback hell"
  • Use for/while loops and if statements, rather than needing the async package
  • Return result to callback using return
  • Use try/catch to handle async errors
  • Pass sync/async errors to the callback automatically (reducing boilerplate), or with throw
  • Compatible with the Node callback convention, as well as Promises
  • watt functions can be called with callbacks, you don't have to change your API

Before watt:

function copyFile (source, dest, callback) {
  fs.exists(source, function (exists) {
    if (!exists) return callback('Source does not exist')
    fs.exists(dest, function (exists) {
      if (exists) return callback('Dest already exists')
      fs.readFile(source, function (err, data) {
        if (err) return callback(err)
        fs.writeFile(dest, data, function (err) {
          return callback(err)
        })
      })
    })
  })
}

After watt:

var copyFile = watt(function * (source, dest, next) {
  if (!(yield fs.exists(source, next.arg(0)))) throw 'Source does not exist'
  if (yield fs.exists(dest, next.arg(0))) throw 'Dest already exists'
  var data = yield fs.readFile(source, next)
  yield fs.writeFile(dest, data, next)
})

Both of these can be called with:

copyFile('a', 'b', function (err) {
  if (err) return console.error(err)
  console.log('copied file')
})

Usage

npm install watt

watt( generatorFn([args...],next), [opts] )

Wraps a generator function and returns a callable function. The returned function can be called with fn([args...], [callback]), and ([args...], next) will be passed to generatorFn.

The user-supplied callback is removed from args and will not be passed to the generator. callback will be automatically called with callback(error, returned) after the generator returns or throws an error. If no callback is supplied (the last argument is not a function), a Promise will be returned instead. Note: if you don't want the last argument to be treated as a callback even if it is a function, you may set the noCallback option (see below).

In the generator, yield should be called to wait for an async thing to happen, and next should be called to resume the generator. Alternatively, if a Promise is passed to yield, the generator will automatically resume once the Promise resolves (or will throw an error if it rejects).

opts may be an object with the following properties:

{
  noCallback: Boolean, // default: false
  // if true, the user-supplied `callback` will not be removed from the arguments,
  // and will not be called after the generator returns or throws an errors

  prepend: Boolean, // default: false
  // if true, the generator is called with `generator(next, args...)`
  // instead of `generator(args..., next)`. This can be useful for functions
  // that accept varying numbers of arguments

  context: Object, // default: caller scope
  // sets the scope for the generator to run in. Similar to binding a function
  // with fn.bind(context)
}

next(error, result)

The next function is passed to watt generators, and is used to unpause the generator after it has yielded. next should be passed to async functions to "return" their results to the generator and resume execution.

If error is truthy, the generator will throw an error. Otherwise, result will be passed to the most recent yield expression.


next.error(error)

If error is truthy, the generator will throw an error. This is useful when you want your generator to throw an error after an error event. For example:

var stream = fs.createReadStream('file.txt')
stream.on('error', next.error)
stream.pipe(someWritableStream)
yield someWritableStream.on('end', next)

In this example, if stream encounters an error while we are waiting for it to pipe to someWritableStream, we will abort waiting for the piping to finish and will throw the error.


next.arg(n, [ignoreError])

A function that returns a callback which can be supplied to async functions to get the nth argument. Used as an alternative to next, which defaults to the 1st argument (the 0th argument is the error).

If ignoreError is truthy, the 0th argument won't be thrown as an error. If n is 0, ignoreError is implied to be true.

For example if we want to call request(url, cb) which calls cb with cb(err, res, body), and we want the body, we can do:

var body = yield request(url, next.arg(2))

next.args()

A callback which can be supplied to async functions to get all of the arguments passed to it. This function does not do any automatic error handling, since the error will be included in the arguments returned. The result returned is the function's arguments object.

Example:

var args = yield fs.readFile('file.txt', next.args)
var error = args[0]
var data = args[1]

next.parallel(), next.sync()

Call next.parallel() to execute async calls in parallel, then call yield next.sync() to wait for these tasks to finish. The result of next.sync() will be an array of the result values of tasks, in the order they were spawned. next.sync() will unyield as soon as a task gives an error, or when all the tasks have finished.

next.parallel() returns a callback similar to next, which treats the first value as an error and the second as the return value. Like next, this callback also has the arg and args properties.

Note that you should not yield before a parallel call since we don't want the generator to block until the call to sync.

Example:

for (var i = 0; i < 5; i++) {
  // waits a random amount of time, then returns i
  setTimeout(next.parallel().arg(0), Math.random() * 1000, i)
}
// wait until all timeouts have finished
var res = yield next.sync()
// res is: [ 0, 1, 2, 3, 4 ]

watt.wrapAll(object, [opts], [names...])

Wraps generator function properties of object. Each wrapped generator function gets bound to the context of object. If no values are specified for names, all generator function properties are wrapped. If one or more strings are specified for names, only the properties with those keys will be wrapped.

opts can be an options object that will be passed to watt().

This can be useful for wrapping generator methods of a class (call watt.wrapAll(this) in the constructor).

var watt = require('watt')

class MyClass {
  constructor () {
    this.a = 5

    // do this to wrap both 'foo' and 'bar'
    watt.wrapAll(this)

    // do this to wrap only 'foo'
    watt.wrapAll(this, 'foo')
  }

  // remember to prefix with * to make a generator
  * foo (next) {
    yield doAsyncThing(this.a, next)
    return yield doAnotherAsyncThing(next)
  }

  * bar (next) {
    yield foo()
    return yield doAnotherAsyncThing(next)
  }

  var mc = new MyClass()
  mc.foo((err, res) => { ... })
}

Examples

Iterating through an array

Iterate through an array of file paths until we find one that exists.

Before watt:

Without watt, we need to use the async module.

var async = require('async')

function firstExisting (paths, cb) {
  async.eachSeries(paths, function (path, next) {
    fs.exists(path, function (err, exists) {
      if (err) return cb(err)
      if (exists) return cb(path)
      next()
    })
  })
}

After watt:

With watt, we can use a standard Javascript for loop.

var async = require('watt')

var firstExisting = async(function * (paths, next) {
  for (var path of paths) {
    if (yield fs.exists(path, next)) return path
  }
})

Iterating through a range

Print numbers from 0 to n, one per second.

Before watt:

Without watt, we need to use recursion. This isn't too complex, however it is slightly less readable than it would be if it were synchronous.

var async = require('async')

function countUp (n, cb) {
  function next (i) {
    setTimeout(function () {
      console.log(i)
      if (i < n) next(i + 1)
    }, 1000)
  }
  next(0)
}

After watt:

With watt, we can use a standard Javascript for loop.

var async = require('watt')

var countUp = async(function * (n, next) {
  for (var i = 0; i <= n; i++) {
    yield setTimeout(next, 1000)
    console.log(i)
  }
})

if statements

An ugly part of using async callbacks is that the syntax gets ugly when branching. Consider a function that calls an async function foo, then depending on the result maybe calls bar, then finally calls baz:

Before watt:

function myAsyncFunc (cb) {
  foo(function (err, res) {
    if (err) return cb(err)
    if (res) {
      bar(function (err) {
        if (err) return cb(err)
        baz(cb)
      })
      return
    }
    baz(cb)
  })
}

After watt:

var myAsyncFunc = async(function * (next) {
  var res = yield foo(next)
  if (res) yield bar(next)
  return yield baz(next)
}

Promises

watt works well with Promises. If you yield a promise, its result will be returned if is resolves, or its error will be thrown if it is rejected.

Additionally, watt functions return Promises if a callback is not provided, so you can call them from another watt generator without providing the next argument.

var foo = async(function * (next) {
  setTimeout(next, 1000)
  return 'aslfdkj'
}

var bar = async(function * (next) {
  return yield foo() // 'next' not necessary
})

// callers can use the Promise API instead of a callback if they want
bar().then(res => console.log(res))

Alternatives

- co

co is similar to watt, but the main difference is that it only works with Promises. It requires that you convert callback functions to return Promises before you can call them, and it does not let you wrap generators with a callback API.

More Repositories

1

gitbanner

🎈 Generates a git repo to show a cool banner on your Github profile
JavaScript
596
star
2

webcoin

πŸŒπŸ’° SPV Bitcoin client for Node.js and the browser
JavaScript
412
star
3

electron-webrtc

βš› Use WebRTC in Node.js via a hidden Electron process
JavaScript
316
star
4

mercury

πŸ’± Decentralized cryptocurrency exchange
Java
159
star
5

peer-exchange

πŸ‘©β†”οΈπŸ‘¨ Decentralized peer discovery and signaling
JavaScript
152
star
6

node-hackrf

πŸ“» Control a HackRF device (e.g. Jawbreaker, HackRF One, or Rad1o) from Node.js
C
146
star
7

DCPU-16

πŸ’Ύ A javascript emulator for DCPU-16 (the computer system in Mojang's new game, 0x10c). Works in browsers and Node.
JavaScript
92
star
8

rust-bitcoin-script

Inline Bitcoin scripts in Rust
Rust
86
star
9

js-tendermint

A JS light client for Tendermint blockchains
JavaScript
72
star
10

coins

Cryptocurrency middleware for Lotion
JavaScript
64
star
11

redstone

πŸ”» A distributed, infinite-player Minecraft server that runs on Node.js.
CoffeeScript
61
star
12

hackrf-stream

A stream interface to receive and transmit on a HackRF radio from Node.js
JavaScript
54
star
13

electron-eval

Run code inside a hidden Electron window
JavaScript
52
star
14

0x10code

πŸ’» A website for sharing and developing code for DCPU16, from Mojang's game 0x10c.
JavaScript
49
star
15

bitcoin-net

🌐 Bitcoin networking that works in Node and the browser
JavaScript
23
star
16

tendermint-node

Run a Tendermint full node from Node.js
JavaScript
22
star
17

blockchain-spv

β—Ύβ—Ύβ—Ύ Stores blockchain headers and verifies transactions with SPV
JavaScript
22
star
18

if

Conditional branching for Node.js
JavaScript
22
star
19

muta

Mutate your objects without mutating your objects
JavaScript
19
star
20

bitcoin-protocol

πŸ”£ Bitcoin network protocol streams
JavaScript
14
star
21

bitcoin-merkle-proof

🌲 Build and verify Bitcoin Merkle proofs
JavaScript
12
star
22

htlc

Hashed TimeLock Contracts for Coins/Lotion
JavaScript
11
star
23

node-bitcoind

Spawn a Bitcoin Core full node from Node.js
JavaScript
11
star
24

old

Make the 'new' keyword optional for ES6 classes
JavaScript
11
star
25

blockchain-download

Download blockchain data from peers
JavaScript
11
star
26

filecoin-whitepaper

NOTICE: This is not associated with the Filecoin project made by Protocol Labs, and is not being actively worked on
HTML
10
star
27

js-hyperx

Atom syntax highlighting for hyperx
CoffeeScript
10
star
28

webcoin-bitcoin

Bitcoin parameters for Webcoin
JavaScript
8
star
29

modbox

Securely box untrusted JS modules, with CPU and memory limiting
JavaScript
8
star
30

proxmise

Proxied Promises - Create async getters for arbitrary object paths
JavaScript
8
star
31

blockchain-state

A writable stream for applications that consume blocks
JavaScript
6
star
32

coinmarketcat

CryptoKitties market data
JavaScript
6
star
33

bitcoin-inventory

Exchange transactions with peers
JavaScript
5
star
34

bitcoin-wallet

Simple HD wallet
JavaScript
5
star
35

node-sock

Simple networking for Node.js
CoffeeScript
4
star
36

deterministic-json

Deterministic JSON parse/stringify, with support for Buffers
JavaScript
4
star
37

event-cleanup

Wrap an EventEmitter for easy listener cleanup
JavaScript
4
star
38

bitcoin-filter

Bitcoin connection Bloom filtering (BIP37)
JavaScript
4
star
39

bitcoin-util

Utility functions for Bitcoin hashes and targets
JavaScript
4
star
40

on-object

Register many EventEmitter listeners at once using objects
JavaScript
4
star
41

js-crypto

JS equivalent of Tendermint's go-crypto package
JavaScript
4
star
42

staking

Proof-of-stake middleware for Lotion
JavaScript
4
star
43

airwave

An SDR application built on Electron
JavaScript
3
star
44

peerhub

Peer discovery and signalling for WebRTC P2P networks
JavaScript
3
star
45

fft-stream

A stream that transforms SDR I/Q samples into the frequency domain
JavaScript
2
star
46

species

My entry for the 24th Ludum Dare compo.
JavaScript
2
star
47

ipfs-code-viewer

View code in IPFS with automatic syntax highlighting
CSS
2
star
48

qwoptimizer

AI that learns to play QWOP using machine learning
JavaScript
2
star
49

outfitly

The social network for clothes. This app uses node.js, express, and MongoDB.
JavaScript
2
star
50

toronto

A lisp that compiles to JS
JavaScript
1
star
51

webcoin-bitcoin-testnet

Bitcoin testnet3 parameters for webcoin
JavaScript
1
star
52

dstate

Store changing state data as commits that can be rolled back
JavaScript
1
star
53

js-merkleeyes

Client for Tendermint's merkleeyes KV store
JavaScript
1
star
54

tenderdash

A dashboard for your Tendermint node
JavaScript
1
star
55

mongorest

Connect/Express middleware that lets you easily provide a REST API for your MongoDB data.
JavaScript
1
star
56

npmscan

Scans NPM for available package names
JavaScript
1
star
57

aptt-blog

The official blog of Aptt.
1
star
58

versionbits

Track Bitcoin versionbits deployments (BIP9)
JavaScript
1
star
59

number-script

Ordinal JavaScript
JavaScript
1
star
60

aoc-2019

Advent of Code 2019
Rust
1
star
61

mercuryex.com

The website for Mercury
HTML
1
star
62

versionbits-web

A web viewer for Bitcoin versionbits deployments
JavaScript
1
star
63

webcoin-bridge

πŸŒ‰ Bridge connections from the Websocket/WebRTC network to the TCP network
JavaScript
1
star