thunks
A small and magical composer for all JavaScript asynchronous.
中文说明
thunks 的作用域和异常处理设计
Compatibility
ES5+, support node.js and browsers.
Summary
- thunks
- Compatibility
- Summary
- Implementations
- What is a thunk
- Demo
- Installation
- API
- thunks([scope])
- thunks.pruneErrorStack
- thunks.onerror(error)
- Class thunks.Scope
- thunk(thunkable)
- thunk.all(obj)
- thunk.all(thunkable1, ..., thunkableN)
- thunk.seq([thunkable1, ..., thunkableN])
- thunk.seq(thunkable1, ..., thunkableN)
- thunk.race([thunkable1, ..., thunkableN])
- thunk.race(thunkable1, ..., thunkableN)
- thunk.thunkify(fn)
- thunk.lift(fn)
- thunk.promise(thunkable)
- thunk.persist(thunkable)
- thunk.delay(delay)
- thunk.stop([message])
- thunk.cancel()
- TypeScript Typings
- What functions are thunkable
- License
Implementations
- Toa A powerful web framework rely on thunks.
- T-man Super test manager for JavaScript.
- thunk-redis The fastest thunk/promise-based redis client, support all redis features.
- thunk-disque A thunk/promise-based disque client.
- thunk-stream Wrap a readable/writable/duplex/transform stream to a thunk.
- thunk-queue A thunk queue for uncertainty tasks evaluation.
- thunk-loop Asynchronous tasks loop (while (true) { ... }).
- thunk-mocha Enable support for generators in Mocha with backward compatibility.
- thunk-ratelimiter The fastest abstract rate limiter.
- thunk-workers Thunk-based task scheduler that executes synchrounous and/or asynchronous tasks under concurrency control.
- file-cache Read file with caching, rely on thunks.
And a mountain of applications in server-side or client-side.
What is a thunk
-
ALGOL thunks in 1961
-
thunk
is a function that encapsulates synchronous or asynchronous code inside.
-
thunk
accepts only one callback
function as an arguments, which is a CPS function.
-
thunk
returns another thunk
function after being called, for chaining operations.
-
thunk
passes the results into a callback
function after being excuted.
-
If the return value of callback
is a thunk
function, then it will be executed first and its result will be sent to another thunk
for excution, or it will be sent to another new thunk
function as the value of the computation.
Demo
with thunk function
const thunk = require('thunks')()
const fs = require('fs')
thunk(function (done) {
fs.stat('package.json', done)
})(function (error, res) {
console.log(error, res)
})
with async function
thunk(async function () {
console.log(await Promise.resolve('await promise in an async function'))
try {
await new Promise((resolve, reject) => {
setTimeout(() => reject('catch promise error in async function'), 1000)
})
} catch (err) {
console.log(err)
}
})()
with generator function
const thunk = require('thunks')()
const fs = require('fs')
const size = thunk.thunkify(fs.stat)
thunk(function * () {
console.log(yield size('thunks.js'))
console.log(yield size('package.json'))
console.log(yield async () => 'yield an async function in generator function')
console.log(yield function * () { return 'yield an async function in generator function' })
console.log(yield thunk.all([
size('thunks.js'),
size('package.json')
]))
})()
chain, sequential, parallel
const thunk = require('thunks')()
const fs = require('fs')
const size = thunk.thunkify(fs.stat)
size('.gitignore')(function (error, res) {
console.log(error, res)
return size('thunks.js')
})(function (error, res) {
console.log(error, res)
return size('package.json')
})(function (error, res) {
console.log(error, res)
})
thunk.seq([
size('.gitignore'),
size('thunks.js'),
size('package.json')
])(function (error, res) {
console.log(error, res)
})
thunk.all([
size('.gitignore'),
size('thunks.js'),
size('package.json')
])(function (error, res) {
console.log(error, res)
})
Installation
Node.js:
npm install thunks
Bower:
bower install thunks
browser:
<script src="/pathTo/thunks.js"></script>
API
const thunks = require('thunks')
const { thunks, thunk, slice, Scope, isAsyncFn, isGeneratorFn, isThunkableFn } = from 'thunks'
thunks([scope])
Matrix of thunk
, it generates a thunkFunction
factory (named thunk
) with it's scope.
"scope" refers to the running evironments thunk
generated(directly or indirectly) for all child thunk functions.
- Here's how you create a basic
thunk
, any exceptions would be passed the next child thunk function:
const thunk = thunks()
- Here's the way to create a
thunk
listening to all exceptions in current scope with onerror
, and it will make sure the exceptions are not being passed to the followed child thunk function, unless onerror
function returns true
.
const thunk = thunks(function (error) { console.error(error) })
Equals:
const scope = new thunks.Scope(function (error) { console.error(error) })
const thunk = thunks(scope)
- Create a
thunk
with onerror
, onstop
and debug
listeners. Results of this thunk
would be passed to debug
function first before passing to the followed child thunk function.
const thunk = thunks({
onstop: function (sig) { console.log(sig) },
onerror: function (error) { console.error(error) },
debug: function () { console.log.apply(console, arguments) }
})
Equals:
const scope = new thunks.Scope({
onstop: function (sig) { console.log(sig) },
onerror: function (error) { console.error(error) },
debug: function () { console.log.apply(console, arguments) }
})
const thunk = thunks(scope)
The context of onerror
, onstop
and debug
is a scope
.
Even multiple thunk
main functions with different scopes are composed,
each scope would be separate from each other,
which means, onerror
, onstop
and debug
would not run in other scopes.
thunks.pruneErrorStack
Default to true
, means it will prune error stack message.
thunks.onerror(error)
Default to null
, it is a global error handler.
Class thunks.Scope
const scope = new thunks.Scope({
onstop: function (sig) { assert.strictEqual(this, scope) },
onerror: function (error) { assert.strictEqual(this, scope) },
debug: function () { assert.strictEqual(this, scope) }
})
const thunk = thunks(scope)
thunk(thunkable)
This is the thunkFunction
factory, to create new thunkFunction
functions.
The parameter thunkable
value could be:
- a
thunkFunction
function, by calling this function a new thunkFunction
function will be returned
let thunk1 = thunk(1)
thunk(thunk1)(function (error, value) {
console.log(error, value)
})
- a thunkLike function
function (callback) {}
, when called, passes its results to the next thunkFunction
function
thunk(function (callback) {
callback(null, 1)
})(function (error, value) {
console.log(error, value)
})
- a Promise object, results of Promise would be passed to a new
thunkFunction
function
let promise = Promise.resolve(1)
thunk(promise)(function (error, value) {
console.log(error, value)
})
- objects which implements the method
toThunk
let obj = {
toThunk: function () {
return function (done) { done(null, 1) }
}
}
thunk(obj)(function (error, value) {
console.log(error, value)
})
- objects which implement the method
toPromise
const Rx = require('rxjs')
thunk(Rx.Observable.fromPromise(Promise.resolve(123)))(function (error, value) {
console.log(error, value)
})
- Generator and Generator Function, like
co
, but yield
anything
thunk(function * () {
var x = yield 10
return 2 * x
})(function * (error, res) {
console.log(error, res)
return yield thunk.all([1, 2, thunk(3)])
})(function * (error, res) {
console.log(error, res)
return yield thunk.all({
name: 'test',
value: thunk(1)
})
})(function (error, res) {
console.log(error, res)
})
- async/await function
thunk(async function () {
console.log(await Promise.resolve('await promise in an async function'))
try {
await new Promise((resolve, reject) => {
setTimeout(() => reject('catch promise error in async function'), 1000)
})
} catch (err) {
console.log(err)
}
})(function * () {
console.log(yield async () => 'yield an async function in generator function')
})()
- values in other types that would be valid results to pass to a new child thunk function
thunk(1)(function (error, value) {
console.log(error, value)
})
thunk([1, 2, 3])(function (error, value) {
console.log(error, value)
})
You can also run with this
:
thunk.call({x: 123}, 456)(function (error, value) {
console.log(error, this.x, value)
return 'thunk!'
})(function (error, value) {
console.log(error, this.x, value)
})
thunk.all(obj)
thunk.all(thunkable1, ..., thunkableN)
Returns a child thunk function.
obj
can be an array or an object that contains any value. thunk.all
will transform value to a child thunk function and excute it in parallel. After all of them are finished, an array containing results(in its original order) would be passed to the a new child thunk function.
thunk.all([
thunk(0),
function * () { return yield 1 },
2,
thunk(function (callback) { callback(null, [3]) })
])(function (error, value) {
console.log(error, value)
})
thunk.all({
a: thunk(0),
b: thunk(1),
c: 2,
d: thunk(function (callback) { callback(null, [3]) })
})(function (error, value) {
console.log(error, value)
})
You may also write code like this:
thunk.all.call({x: [1, 2, 3]}, [4, 5, 6])(function (error, value) {
console.log(error, this.x, value)
return 'thunk!'
})(function (error, value) {
console.log(error, this.x, value)
})
thunk.seq([thunkable1, ..., thunkableN])
thunk.seq(thunkable1, ..., thunkableN)
Returns a child thunk function.
thunkX
can be any value, thunk.seq
will transform value to a child thunk function and excute it in order. After all of them are finished, an array containing results(in its original order) would be passed to the a new child thunk function.
thunk.seq([
function (callback) {
setTimeout(function () {
callback(null, 'a', 'b')
}, 100)
},
thunk(function (callback) {
callback(null, 'c')
}),
[thunk('d'), function * () { return yield 'e' }],
function (callback) {
should(flag).be.eql([true, true])
flag[2] = true
callback(null, 'f')
}
])(function (error, value) {
console.log(error, value)
})
or
thunk.seq(
function (callback) {
setTimeout(function () {
callback(null, 'a', 'b')
}, 100)
},
thunk(function (callback) {
callback(null, 'c')
}),
[thunk('d'), thunk('e')],
function (callback) {
should(flag).be.eql([true, true])
flag[2] = true
callback(null, 'f')
}
)(function (error, value) {
console.log(error, value)
})
You may also write code like this:
thunk.seq.call({x: [1, 2, 3]}, 4, 5, 6)(function (error, value) {
console.log(error, this.x, value)
return 'thunk!'
})(function (error, value) {
console.log(error, this.x, value)
})
thunk.race([thunkable1, ..., thunkableN])
thunk.race(thunkable1, ..., thunkableN)
Returns a child thunk function with the value or error from one first completed.
thunk.thunkify(fn)
Returns a new function that would return a child thunk function
Transform a fn
function which is in Node.js style into a new function.
This new function does not accept a callback
as an argument, but accepts child thunk functions.
const thunk = require('thunks')()
const fs = require('fs')
const fsStat = thunk.thunkify(fs.stat)
fsStat('thunks.js')(function (error, result) {
console.log('thunks.js: ', result)
})
fsStat('.gitignore')(function (error, result) {
console.log('.gitignore: ', result)
})
You may also write code with this
:
let obj = {a: 8}
function run (x, callback) {
callback(null, this.a * x)
}
let run = thunk.thunkify.call(obj, run)
run(1)(function (error, result) {
console.log('run 1: ', result)
})
run(2)(function (error, result) {
console.log('run 2: ', result)
})
thunk.lift(fn)
lift
comes from Haskell, it transforms a synchronous function fn
into a new async function.
This new function will accept thunkable
arguments, evaluate them, then run as the original function fn
. The new function returns a child thunk function.
const thunk = require('thunks')()
function calculator (a, b, c) {
return (a + b + c) * 10
}
const calculatorT = thunk.lift(calculator)
let value1 = thunk(2)
let value2 = Promise.resolve(3)
calculatorT(value1, value2, 5)(function (error, result) {
console.log(result)
})
You may also write code with this
:
const calculatorT = thunk.lift.call(context, calculator)
thunk.promise(thunkable)
it transforms thunkable
value to a promise.
const thunk = require('thunks').thunk
thunk.promise(function * () {
return yield Promise.resolve('Hello')
}).then(function (res) {
console.log(res)
})
thunk.persist(thunkable)
it transforms thunkable
value to a persist thunk function, which can be called more than once with the same result(like a promise). The new function returns a child thunk function.
const thunk = require('thunks')()
let persistThunk = thunk.persist(thunk(x))
persistThunk(function (error, result) {
console.log(1, result)
return persistThunk(function (error, result) {
console.log(2, result)
return persistThunk
})
})(function (error, result) {
console.log(3, result)
})
You may also write code with this
:
const persistThunk = thunk.persist.call(context, thunkable)
thunk.delay(delay)
Return a child thunk function, this child thunk function will be called after delay
milliseconds.
console.log('thunk.delay 500: ', Date.now())
thunk.delay(500)(function () {
console.log('thunk.delay 1000: ', Date.now())
return thunk.delay(1000)
})(function () {
console.log('thunk.delay end: ', Date.now())
})
You may also write code with this
:
console.log('thunk.delay start: ', Date.now())
thunk.delay.call(this, 1000)(function () {
console.log('thunk.delay end: ', Date.now())
})
thunk.stop([message])
This will stop control flow process with a message similar to Promise's cancelable(not implemented yet). It will throw a stop signal object.
Stop signal is an object with a message and status === 19
(POSIX signal SIGSTOP) and a special code. Stop signal can be caught by onstop
, and aslo can be caught by try catch
, in this case it will not trigger onstop
.
const thunk = require('thunks')({
onstop: function (res) {
if (res) console.log(res.code, res.status, res)
}
})
thunk(function (callback) {
thunk.stop('Stop now!')
console.log('It will not run!')
})(function (error, value) {
console.log('It will not run!', error)
})
thunk.delay(100)(function () {
console.log('Hello')
return thunk.delay(100)(function () {
thunk.stop('Stop now!')
console.log('It will not run!')
})
})(function (error, value) {
console.log('It will not run!')
})
thunk.cancel()
This will cancel all control flow process in the current thunk's scope.
TypeScript Typings
import * as assert from 'assert'
import { thunk, thunks, isGeneratorFn } from 'thunks'
thunk(function * () {
assert.strictEqual(yield thunks()(1), 1)
assert.ok(isGeneratorFn(function * () {}))
while (true) {
yield function (done) { setTimeout(done, 1000) }
console.log('Dang!')
}
})()
What functions are thunkable
thunks supports so many thunkable objects. There are three kind of functions:
- thunk-like function
function (callback) { callback(err, someValue) }
- generator function
function * () { yield something }
- async/await function
async function () { await somePromise }
thunks can't support common functions (non-thunk-like functions). thunks uses fn.length === 1
to recognize thunk-like functions.
Using a common function in this way will throw an error:
thunk(function () {})(function (err) {
console.log(1, err)
})
thunk(function (a, b) {})(function (err) {
console.log(2, err)
})
thunk(function () { let callback = arguments[0]; callback() })(function (err) {
console.log(3, err)
})
thunk()(function () {
return function () {}
})(function (err) {
console.log(4, err)
})
So pay attention to that. We can't return a non-thunkable function in thunk. If we return a thunkable function, thunk will evaluate it as an asynchronous task.
License
thunks is licensed under the MIT license.
Copyright © 2014-2020 thunks.