首次提交

This commit is contained in:
2026-07-13 12:06:16 +08:00
commit e3c810b1a6
1690 changed files with 228324 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 13.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: |
npm install
- name: Run tests
run: |
npm run test
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
parallel: true
coverage:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
+14
View File
@@ -0,0 +1,14 @@
Copyright (c) 2015, Matteo Collina <matteo.collina@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+115
View File
@@ -0,0 +1,115 @@
# fastseries
![ci][ci-url]
[![npm version][npm-badge]][npm-url]
[![Coverage Status][coveralls-badge]][coveralls-url]
[![Dependency Status][david-badge]][david-url]
Zero-overhead series function call for node.js.
Also supports `each` and `map`!
If you need zero-overhead parallel function call, check out
[fastparallel](http://npm.im/fastparallel).
[![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard)
## Example for series call
```js
var series = require('fastseries')({
// if you want the results, then here you are
results: true
})
series(
{}, // what will be this in the functions
[something, something, something], // functions to call
42, // the first argument of the functions
done // the function to be called when the series ends
)
function late (arg, cb) {
console.log('finishing', arg)
cb(null, 'myresult-' + arg)
}
function something (arg, cb) {
setTimeout(late, 1000, arg, cb)
}
function done (err, results) {
console.log('series completed, results:', results)
}
```
## Example for each and map calls
```js
var series = require('fastseries')({
// if you want the results, then here you are
// passing false disables map
results: true
})
series(
{}, // what will be this in the functions
something, // functions to call
[1, 2, 3], // the first argument of the functions
done // the function to be called when the series ends
)
function late (arg, cb) {
console.log('finishing', arg)
cb(null, 'myresult-' + arg)
}
function something (arg, cb) {
setTimeout(late, 1000, arg, cb)
}
function done (err, results) {
console.log('series completed, results:', results)
}
```
## Caveats
The `done` function will be called only once, even if more than one error happen.
This library works by caching the latest used function, so that running a new series
does not cause **any memory allocations**.
## Benchmarks
Benchmark for doing 3 calls `setImmediate` 1 million times:
```
benchSetImmediate*1000000: 2460.623ms
benchAsyncSeries*1000000: 3064.569ms
benchAsyncEachSeries*1000000: 2913.525ms
benchAsyncMapSeries*1000000: 3020.794ms
benchNeoSeries*1000000: 2617.064ms
benchNeoEachSeries*1000000: 2621.672ms
benchNeoMapSeries*1000000: 2611.294ms
benchTinyEachAsync*1000000: 2706.457ms
benchFastSeries*1000000: 2540.653ms
benchFastSeriesNoResults*1000000: 2538.674ms
benchFastSeriesEach*1000000: 2534.856ms
benchFastSeriesEachResults*1000000: 2545.394ms
```
Benchmarks taken on Node 12.16.1 on a dedicated server.
See [bench.js](./bench.js) for mode details.
## License
ISC
[ci-url]: https://github.com/mcollina/fastseries/workflows/ci/badge.svg
[npm-badge]: https://badge.fury.io/js/fastseries.svg
[npm-url]: https://badge.fury.io/js/fastseries
[coveralls-badge]:https://coveralls.io/repos/mcollina/fastseries/badge.svg?branch=master&service=github
[coveralls-url]: https://coveralls.io/github/mcollina/fastseries?branch=master
[david-badge]: https://david-dm.org/mcollina/fastseries.svg
[david-url]: https://david-dm.org/mcollina/fastseries
+94
View File
@@ -0,0 +1,94 @@
var max = 1000000
var series = require('./')()
var seriesNoResults = require('./')({ results: false })
var async = require('async')
var neo = require('neo-async')
var bench = require('fastbench')
var tinyEachAsync = require('tiny-each-async')
function benchFastSeries (done) {
series(null, [somethingP, somethingP, somethingP], 42, done)
}
function benchFastSeriesNoResults (done) {
seriesNoResults(null, [somethingP, somethingP, somethingP], 42, done)
}
function benchFastSeriesEach (done) {
seriesNoResults(null, somethingP, [1, 2, 3], done)
}
function benchFastSeriesEachResults (done) {
series(null, somethingP, [1, 2, 3], done)
}
function benchAsyncSeries (done) {
async.series([somethingA, somethingA, somethingA], done)
}
function benchAsyncEachSeries (done) {
async.eachSeries([1, 2, 3], somethingP, done)
}
function benchAsyncMapSeries (done) {
async.mapSeries([1, 2, 3], somethingP, done)
}
function benchNeoSeries (done) {
neo.series([somethingA, somethingA, somethingA], done)
}
function benchNeoEachSeries (done) {
neo.eachSeries([1, 2, 3], somethingP, done)
}
function benchNeoMapSeries (done) {
neo.mapSeries([1, 2, 3], somethingP, done)
}
function benchTinyEachAsync (done) {
tinyEachAsync([1, 2, 3], 1, somethingP, done)
}
var nextDone
var nextCount
function benchSetImmediate (done) {
nextCount = 3
nextDone = done
setImmediate(somethingImmediate)
}
function somethingImmediate () {
nextCount--
if (nextCount === 0) {
nextDone()
} else {
setImmediate(somethingImmediate)
}
}
function somethingP (arg, cb) {
setImmediate(cb)
}
function somethingA (cb) {
setImmediate(cb)
}
var run = bench([
benchSetImmediate,
benchAsyncSeries,
benchAsyncEachSeries,
benchAsyncMapSeries,
benchNeoSeries,
benchNeoEachSeries,
benchNeoMapSeries,
benchTinyEachAsync,
benchFastSeries,
benchFastSeriesNoResults,
benchFastSeriesEach,
benchFastSeriesEachResults
], max)
run(run)
+45
View File
@@ -0,0 +1,45 @@
var series = require('./')({
// this is a function that will be called
// when a series completes
released: completed,
// we want results and errors
// passing false will make it faster!
results: true
})
series(
{}, // what will be this in the functions
[something, something, something], // functions to call
42, // the first argument of the functions
next // the function to be called when the series ends
)
function late (arg, cb) {
console.log('finishing', arg)
cb(null, 'myresult-' + arg)
}
function something (arg, cb) {
setTimeout(late, 1000, arg, cb)
}
function next (err, results) {
if (err) {
// do something here!
}
console.log('series completed, results:', results)
series({}, something, [1, 2, 3], done)
}
function done (err, results) {
if (err) {
// do something here!
}
console.log('series completed, results:', results)
}
function completed () {
console.log('series completed!')
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "fastseries",
"version": "2.0.0",
"description": "Zero-overhead asynchronous series/each/map function calls",
"main": "series.js",
"scripts": {
"lint": "standard --verbose | snazzy",
"unit": "tape test.js",
"unit:report": "nyc --reporter=html --reporter=cobertura --reporter=text tape test.js",
"unit:cov": "nyc --reporter=lcovonly tape test.js",
"cov": "nyc --reporter=text tape test.js",
"test:report": "npm run lint && npm run unit:report",
"test": "npm run lint && npm run unit:cov"
},
"pre-commit": [
"test"
],
"repository": {
"type": "git",
"url": "https://github.com/mcollina/fastseries.git"
},
"keywords": [
"series",
"fast",
"async"
],
"author": "Matteo Collina <hello@matteocollina.com>",
"license": "ISC",
"bugs": {
"url": "https://github.com/mcollina/fastseries/issues"
},
"homepage": "https://github.com/mcollina/fastseries",
"devDependencies": {
"async": "^3.2.0",
"fastbench": "^1.0.1",
"neo-async": "^2.6.1",
"nyc": "^15.0.0",
"pre-commit": "^1.2.2",
"snazzy": "^8.0.0",
"standard": "^14.3.1",
"tape": "^4.13.0",
"tiny-each-async": "^2.0.3"
}
}
+155
View File
@@ -0,0 +1,155 @@
'use strict'
var defaults = {
results: true
}
function fastseries (options) {
options = Object.assign({}, defaults, options)
var seriesEach
var seriesList
if (options.results) {
seriesEach = resultEach
seriesList = resultList
} else {
seriesEach = noResultEach
seriesList = noResultList
}
return series
function series (that, toCall, arg, done) {
done = (done || nop).bind(that)
if (toCall.length === 0) {
done.call(that)
} else if (toCall.bind) {
if (that) {
toCall = toCall.bind(that)
}
seriesEach(toCall, arg, done)
} else {
var _list
if (that) {
var length = toCall.length
_list = new Array(length)
for (var i = 0; i < length; i++) {
_list[i] = toCall[i].bind(that)
}
} else {
_list = toCall
}
seriesList(_list, arg, done)
}
}
}
function noResultEach (each, list, cb) {
var i = 0
var length = list.length
release()
function release () {
if (i < length) {
makeCallTwo(each, list[i++], release)
} else {
cb()
}
}
}
function noResultList (list, arg, cb) {
var i = 0
var length = list.length
var makeCall
if (list[0].length === 1) {
makeCall = makeCallOne
} else {
makeCall = makeCallTwo
}
release()
function release () {
if (i < length) {
makeCall(list[i++], arg, release)
} else {
cb()
}
}
}
function resultEach (each, list, cb) {
var i = 0
var length = list.length
var results = new Array(length)
release(null, null)
function release (err, result) {
if (err) {
cb(err)
return
}
if (i > 0) {
results[i - 1] = result
}
if (i < length) {
makeCallTwo(each, list[i++], release)
} else {
cb(null, results)
}
}
}
function resultList (list, arg, cb) {
var i = 0
var length = list.length
var makeCall
if (list[0].length === 1) {
makeCall = makeCallOne
} else {
makeCall = makeCallTwo
}
var results = new Array(length)
release(null, null)
function release (err, result) {
if (err) {
cb(err)
return
}
if (i > 0) {
results[i - 1] = result
}
if (i < length) {
makeCall(list[i++], arg, release)
} else {
cb(null, results)
}
}
}
function makeCallOne (cb, arg, release) {
cb(release)
}
function makeCallTwo (cb, arg, release) {
cb(arg, release)
}
function nop () { }
module.exports = fastseries
+368
View File
@@ -0,0 +1,368 @@
'use strict'
var test = require('tape')
var series = require('./')
test('basically works', function (t) {
t.plan(7)
var instance = series()
var count = 0
var obj = {}
instance(obj, [build(0), build(1)], 42, function done () {
t.equal(count, 2, 'all functions must have completed')
})
function build (expected) {
return function something (arg, cb) {
t.equal(obj, this)
t.equal(arg, 42)
t.equal(expected, count)
setImmediate(function () {
count++
cb()
})
}
}
})
test('without this', function (t) {
t.plan(7)
var instance = series()
var count = 0
instance(null, [build(0), build(1)], 42, function done () {
t.equal(count, 2, 'all functions must have completed')
})
function build (expected) {
return function something (arg, cb) {
t.equal(undefined, this)
t.equal(arg, 42)
t.equal(expected, count)
setImmediate(function () {
count++
cb()
})
}
}
})
test('accumulates results', function (t) {
t.plan(7)
var instance = series()
var count = 0
var obj = {}
instance(obj, [something, something], 42, function done (err, results) {
t.notOk(err, 'no error')
t.equal(count, 2, 'all functions must have completed')
t.deepEqual(results, [1, 2])
})
function something (arg, cb) {
t.equal(obj, this)
t.equal(arg, 42)
setImmediate(function () {
count++
cb(null, count)
})
}
})
test('fowards errs', function (t) {
t.plan(3)
var instance = series()
var count = 0
var obj = {}
instance(obj, [somethingErr, something], 42, function done (err, results) {
t.ok(err, 'error exists')
t.equal(err.message, 'this is an err!')
t.equal(count, 1, 'only the first function must have completed')
})
function something (arg, cb) {
setImmediate(function () {
count++
cb(null, count)
})
}
function somethingErr (arg, cb) {
setImmediate(function () {
count++
cb(new Error('this is an err!'))
})
}
})
test('does not forward errors or result with results:false flag', function (t) {
t.plan(7)
var instance = series({
results: false
})
var count = 0
var obj = {}
instance(obj, [something, something], 42, function done (err, results) {
t.equal(err, undefined, 'no err')
t.equal(results, undefined, 'no err')
t.equal(count, 2, 'all functions must have completed')
})
function something (arg, cb) {
t.equal(obj, this)
t.equal(arg, 42)
setImmediate(function () {
count++
cb()
})
}
})
test('should call done iff an empty is passed', function (t) {
t.plan(1)
var instance = series()
var obj = {}
instance(obj, [], 42, function done () {
t.pass()
})
})
test('each support', function (t) {
t.plan(7)
var instance = series()
var count = 0
var obj = {}
var args = [1, 2, 3]
var i = 0
instance(obj, something, [].concat(args), function done () {
t.equal(count, 3, 'all functions must have completed')
})
function something (arg, cb) {
t.equal(obj, this, 'this matches')
t.equal(args[i++], arg, 'the arg is correct')
setImmediate(function () {
count++
cb()
})
}
})
test('each errors', function (t) {
t.plan(2)
var instance = series()
var obj = {}
var args = [1, 2, 3]
var err = new Error('kaboom')
instance(obj, something, [].concat(args), function done (_err) {
t.equal(err, _err)
})
function something (arg, cb) {
t.pass('something called')
cb(err)
}
})
test('each without this', function (t) {
t.plan(7)
var instance = series()
var count = 0
var args = [1, 2, 3]
var i = 0
instance(null, something, [].concat(args), function done () {
t.equal(count, 3, 'all functions must have completed')
})
function something (arg, cb) {
t.equal(undefined, this, 'this matches')
t.equal(args[i++], arg, 'the arg is correct')
setImmediate(function () {
count++
cb()
})
}
})
test('call the callback with the given this', function (t) {
t.plan(1)
var instance = series()
var obj = {}
instance(obj, [build(), build()], 42, function done () {
t.equal(obj, this, 'this matches')
})
function build () {
return function something (arg, cb) {
setImmediate(cb)
}
}
})
test('call the callback with the given this with no results', function (t) {
t.plan(1)
var instance = series({ results: false })
var obj = {}
instance(obj, [build(), build()], 42, function done () {
t.equal(obj, this, 'this matches')
})
function build () {
return function something (arg, cb) {
setImmediate(cb)
}
}
})
test('call the callback with the given this with no data', function (t) {
t.plan(1)
var instance = series()
var obj = {}
instance(obj, [], 42, function done () {
t.equal(obj, this, 'this matches')
})
})
test('support no final callback', function (t) {
t.plan(6)
var instance = series()
var count = 0
var obj = {}
instance(obj, [build(0), build(1)], 42)
function build (expected) {
return function something (arg, cb) {
t.equal(obj, this)
t.equal(arg, 42)
t.equal(expected, count)
setImmediate(function () {
count++
cb()
})
}
}
})
test('call without arg if there is no arg with no results', function (t) {
t.plan(3)
var instance = series({
results: false
})
var count = 0
var obj = {}
instance(obj, [something, something], 42, function done () {
t.equal(count, 2, 'all functions must have completed')
})
function something (cb) {
t.equal(obj, this)
setImmediate(function () {
count++
cb()
})
}
})
test('call without arg if there is no arg with results', function (t) {
t.plan(3)
var instance = series()
var count = 0
var obj = {}
instance(obj, [something, something], 42, function done () {
t.equal(count, 2, 'all functions must have completed')
})
function something (cb) {
t.equal(obj, this)
setImmediate(function () {
count++
cb()
})
}
})
test('each support with nothing to process', function (t) {
t.plan(2)
var instance = series()
var obj = {}
var args = []
instance(obj, something, args, function done (err, results) {
t.error(err)
t.deepEqual(results, [], 'empty results')
})
function something (arg, cb) {
t.fail('this should never happen')
}
})
test('each without results support with nothing to process', function (t) {
t.plan(1)
var instance = series({ results: false })
var obj = {}
var args = []
instance(obj, something, args, function done () {
t.pass('done called')
})
function something (arg, cb) {
t.fail('this should never happen')
}
})
test('each without results', function (t) {
t.plan(7)
var instance = series({
results: false
})
var count = 0
var obj = {}
var args = [1, 2, 3]
var i = 0
instance(obj, something, [].concat(args), function done () {
t.equal(count, 3, 'all functions must have completed')
})
function something (arg, cb) {
t.equal(obj, this, 'this matches')
t.equal(args[i++], arg, 'the arg is correct')
setImmediate(function () {
count++
cb()
})
}
})