nonblocking-array
Version:
Provides nonblocking functions of various Array methods. Unlike the standard array methods that execute array iterations in its entirety in one cycle of the event loop and may block the event loop on large arrays, these functions execute each iteration of
119 lines (113 loc) • 3.64 kB
JavaScript
function isNodeJs() {
return typeof window === "undefined" && typeof process !== "undefined";
}
/**
* Works similar to Array.prototype.map but executes each iteration of the array asynchronously.
* This allows the execution to yield to other tasks thereby not blocking the event loop.
* @param {Array} array
* @param {Function} callback
* @returns A new array populated with the results of the callback function
*/
function map(array, callback, thisArg) {
return new Promise((resolve, reject) => {
if (!Array.isArray(array))
reject(new TypeError("Invalid argument. Must be an array"));
const newArray = [];
function help(index) {
if (index < array.length) {
Promise.resolve(callback.call(thisArg, array[index], index, array))
.then((result) => {
newArray.push(result);
if (isNodeJs()) setImmediate(help.bind(null, index + 1));
else setTimeout(help.bind(null, index + 1), 0);
})
.catch((error) => {
reject(error);
});
} else {
resolve(newArray);
}
}
help(0);
});
}
function filter(array, callback, thisArg) {
return new Promise((resolve, reject) => {
if (!Array.isArray(array))
reject(new TypeError("Invalid argument. Must be an array"));
const newArray = [];
function help(index) {
if (index < array.length) {
Promise.resolve(callback.call(thisArg, array[index], index, array))
.then((result) => {
if (result) newArray.push(array[index]);
if (isNodeJs()) setImmediate(help.bind(null, index + 1));
else setTimeout(help.bind(null, index + 1), 0);
})
.catch((error) => {
reject(error);
});
} else {
resolve(newArray);
}
}
help(0);
});
}
function forEach(array, callback, thisArg) {
return new Promise((resolve, reject) => {
if (!Array.isArray(array))
reject(new TypeError("Invalid argument. Must be an array"));
function help(index) {
if (index < array.length) {
Promise.resolve(callback.call(thisArg, array[index], index, array))
.then((result) => {
if (isNodeJs()) setImmediate(help.bind(null, index + 1));
else setTimeout(help.bind(null, index + 1), 0);
})
.catch((error) => {
reject(error);
});
} else {
resolve(undefined);
// return;
}
}
help(0);
});
}
function reduce(array, callback, initialValue) {
return new Promise((resolve, reject) => {
if (!Array.isArray(array))
reject(new TypeError("Invalid argument. Must be an array"));
if (
(initialValue == undefined || initialValue == null) &&
array.length === 0
)
reject(new TypeError("Array must not be empty"));
let accumulator =
initialValue == undefined || initialValue == null
? array[0]
: initialValue;
function help(index) {
if (index < array.length) {
Promise.resolve(callback(accumulator, array[index], index, array))
.then((result) => {
accumulator = result;
if (isNodeJs()) setImmediate(help.bind(null, index + 1));
else setTimeout(help.bind(null, index + 1), 0);
})
.catch((error) => {
reject(error);
return;
});
} else {
resolve(accumulator);
return;
}
}
if (initialValue != undefined || initialValue != null) help(0);
else help(1);
});
}
module.exports = { map, forEach, reduce, filter };