rubico
Version:
[a]synchronous functional programming
261 lines (255 loc) • 7.61 kB
JavaScript
const isPromise = require('./_internal/isPromise')
const __ = require('./_internal/placeholder')
const curry2 = require('./_internal/curry2')
const isArray = require('./_internal/isArray')
const isGeneratorFunction = require('./_internal/isGeneratorFunction')
const isAsyncGeneratorFunction = require('./_internal/isAsyncGeneratorFunction')
const arrayForEach = require('./_internal/arrayForEach')
const objectForEach = require('./_internal/objectForEach')
const iteratorForEach = require('./_internal/iteratorForEach')
const asyncIteratorForEach = require('./_internal/asyncIteratorForEach')
const symbolIterator = require('./_internal/symbolIterator')
const symbolAsyncIterator = require('./_internal/symbolAsyncIterator')
const arrayForEachSeries = require('./_internal/arrayForEachSeries')
const objectForEachSeries = require('./_internal/objectForEachSeries')
const iteratorForEachSeries = require('./_internal/iteratorForEachSeries')
const asyncIteratorForEachSeries = require('./_internal/asyncIteratorForEachSeries')
// type Collection = Array|Iterable|AsyncIterable|{ forEach: function }|Object
// _forEach(collection Collection, callback function) -> collection Collection
const _forEach = function (collection, callback) {
if (isArray(collection)) {
return arrayForEach(collection, callback)
}
if (collection == null) {
return collection
}
if (typeof collection.forEach == 'function') {
collection.forEach(callback)
return collection
}
if (typeof collection[symbolIterator] == 'function') {
return iteratorForEach(collection[symbolIterator](), callback)
}
if (typeof collection[symbolAsyncIterator] == 'function') {
return asyncIteratorForEach(collection[symbolAsyncIterator](), callback)
}
if (collection.constructor == Object) {
return objectForEach(collection, callback)
}
return collection
}
/**
* @name forEach
*
* @synopsis
* ```coffeescript [specscript]
* type Iterable = Array|Set|Map|Generator|AsyncGenerator|{ forEach: function }|Object
*
* type SyncOrAsyncCallback = (
* element any,
* indexOrKey number|string|any,
* iter Iterable
* )=>Promise|undefined
*
* iterable Iterable
* cb SyncOrAsyncCallback
*
* forEach(iterable, cb) -> unmodifiedIterable Promise|Iterable
* forEach(cb)(iterable) -> unmodifiedIterable Promise|Iterable
* ```
*
* @description
* Execute a callback function for each element of an iterable, returning the original iterable unmodified.
*
* The following data types are considered to be iterables:
* * `array`
* * `set`
* * `map`
* * `generator`
* * `async generator`
* * `object with .forEach method`
* * `object`
*
* The callback function signature changes depending on the provided iterable.
*
* If the iterable is an array:
* ```coffeescript [specscript]
* callback(element any, index number, iter Array) -> Promise|undefined
* ```
*
* If the iterable is a set:
* ```coffeescript [specscript]
* callback(element any, key any, iter Set) -> Promise|undefined
* ```
*
* If the iterable is a map:
* ```coffeescript [specscript]
* callback(element any, key any, filt Map) -> Promise|undefined
* ```
*
* If the iterable is a generator:
* ```coffeescript [specscript]
* callback(element any) -> Promise|undefined
* ```
*
* If the iterable is an async generator:
* ```coffeescript [specscript]
* callback(element any) -> Promise|undefined
* ```
*
* If the iterable is an object with a `.forEach` method, the callback function signature is defined externally.
*
* If the iterable is a plain object:
* ```coffeescript [specscript]
* callback(element any, key string, iter Object) -> Promise|undefined
* ```
*
* If the callback function is asynchronous, it is executed concurrently.
*
* ```javascript [playground]
* forEach([1, 2, 3, 4, 5], async number => {
* await new Promise(resolve => {
* setTimeout(resolve, 1000)
* })
* console.log(number)
* })
* ```
*
* `forEach` works for arrays.
*
* ```javascript [playground]
* forEach([1, 2, 3, 4, 5], num => console.log(num)) // 1 2 3 4 5
* ```
*
* `forEach` works for objects.
*
* ```javascript [playground]
* forEach({ a: 1, b: 2, c: 3 }, num => console.log(num)) // 1 2 3
* ```
*
* Omit the data argument for a composable API
*
* ```javascript [playground]
* pipe([1, 2, 3, 4, 5], [
* filter(number => number % 2 == 1),
* map(number => number ** 2),
* forEach(console.log), // 1
* // 9
* // 25
* ])
* ```
*
* Any promises passed in argument position are resolved for their values before further execution. This only applies to the eager version of the API.
*
* ```javascript [playground]
* forEach(Promise.resolve([1, 2, 3]), console.log)
* // 1
* // 2
* // 3
* ```
*
* See also:
* * [pipe](/docs/pipe)
* * [tap](/docs/tap)
* * [all](/docs/all)
* * [forEach.series](/docs/forEach.series)
* * [map](/docs/map)
*/
const forEach = function (arg0, arg1) {
if (typeof arg0 == 'function') {
return curry2(_forEach, __, arg0)
}
return isPromise(arg0)
? arg0.then(curry2(_forEach, __, arg1))
: _forEach(arg0, arg1)
}
/**
* @name _forEachSeries
*
* @synopsis
* ```coffeescript [specscript]
* type Collection = Array|Iterable|AsyncIterable|{ forEach: function }|Object
*
* _forEachSeries(collection Collection, callback function) -> collection Promise|Collection
* ```
*/
const _forEachSeries = function (collection, callback) {
if (isArray(collection)) {
return arrayForEachSeries(collection, callback)
}
if (collection == null) {
throw new TypeError(`invalid collection ${collection}`)
}
if (typeof collection[symbolIterator] == 'function') {
return iteratorForEachSeries(collection[symbolIterator](), callback)
}
if (typeof collection[symbolAsyncIterator] == 'function') {
return asyncIteratorForEachSeries(collection[symbolAsyncIterator](), callback)
}
if (collection.constructor == Object) {
return objectForEachSeries(collection, callback)
}
throw new TypeError(`invalid collection ${collection}`)
}
/**
* @name forEach.series
*
* @synopsis
* ```coffeescript [specscript]
* type Iterable = Array|Set|Map|Generator|AsyncGenerator|{ forEach: function }|Object
*
* type SyncOrAsyncCallback = (
* element any,
* indexOrKey number|string|any,
* iter Iterable
* )=>Promise|undefined
*
* iterable Iterable
* cb SyncOrAsyncCallback
*
* forEach(iterable, cb) -> unmodifiedIterable Promise|Iterable
* forEach(cb)(iterable) -> unmodifiedIterable Promise|Iterable
* ```
*
* @description
* [forEach](/docs/forEach) with serial execution.
*
* ```javascript [playground]
* forEach.series([1, 2, 3, 4, 5], async number => {
* await new Promise(resolve => {
* setTimeout(resolve, 1000)
* })
* console.log(number)
* // 1
* // 2
* // 3
* // 4
* // 5
* })
* ```
*
* Any promises passed in argument position are resolved for their values before further execution. This only applies to the eager version of the API.
*
* ```javascript [playground]
* forEach.series(Promise.resolve([1, 2, 3]), console.log)
* // 1
* // 2
* // 3
* ```
*
* See also:
* * [pipe](/docs/pipe)
* * [tap](/docs/tap)
* * [all](/docs/all)
* * [forEach](/docs/forEach)
* * [map](/docs/map)
*/
forEach.series = function forEachSeries(arg0, arg1) {
if (typeof arg0 == 'function') {
return curry2(_forEachSeries, __, arg0)
}
return isPromise(arg0)
? arg0.then(curry2(_forEachSeries, __, arg1))
: _forEachSeries(arg0, arg1)
}
module.exports = forEach