Security News
New Python Packaging Proposal Aims to Solve Phantom Dependency Problem with SBOMs
PEP 770 proposes adding SBOM support to Python packages to improve transparency and catch hidden non-Python dependencies that security tools often miss.
Yielding enumeration replacement functions for async.forEachSeries() and jQuery.each()
v0.1.2
Copyright 2015 Nic Jansma http://nicj.net
Licensed under the MIT license
Serially enumerating over a collection (such as using async.js's
async.forEachSeries()
in Node.js or jQuery's $.each()
in the browser) can lead to performance and
responsiveness issues if processing or looping through the collection takes
too long. In some browsers, enumerating over a large number of elements (or
doing a lot of work on each element) may cause the browser to become unresponsive,
and possibly prompt the user to stop running the script.
breakup.js helps solve this problem by breaking up the enumeration into
time-based chunks, and yielding to the environment if a threshold of time
has passed before continuing. This will help avoid a Long Running Script
dialog in browsers as they are given a chance to update their UI. It is meant
to be a simple, drop-in replacement for async.forEachSeries()
. It also provides
breakup.each()
as a replacement for jQuery.each()
(though the developer may
have to modify code-flow to deal with the asynchronous nature of breakup.js).
breakup.js does this by keeping track of how much time the enumeration has taken
after processing each item. If the enumeration time has passed a threshold (the
default is 50ms, but this can be customized), the enumeration will yield before
resuming. Yielding can be done immediately in environments that support it (such
as process.nextTick()
in Node.js and setImmediate()
in modern browsers), and
will fallback to a setTimeout(..., 4)
in older browsers. This yield will allow
the environment to do any UI and other processing work it wants to do. In browsers,
this will help reduce the chance of a Long Running Script dialog.
breakup.js is primarily meant to be used in a browser environment, as Node.js code is
already asynchronously driven. You won't see a Long Running Script dialog in Node.js. However,
you're welcome to use the breakup Node.js module if you want have more control over how much
time your enumerations take. For example, if you have thousands of items to enumerate
and you want to process them lazily, you could set the threshold to 100ms with a 10000ms
wait time and specify the forceYield
parameter, so other work is prioritized.
Changing async.forEachSeries()
to breakup.forEachSeries()
is as simple as
changing the module name. You may add two additional parameters to fine-tune
the wait time and yield time if you prefer (see Documentation for details).
Changing jQuery.each()
to breakup.each()
requires a bit more work as you
will need to change from waiting for the function to return to waiting for a callback
to fire. See the breakup.each()
for details.
Releases are available for download from GitHub.
Development: breakup.js - 8.1kb
Production: breakup.min.js - 829b (minified / gzipped)
breakup.js is also available as the npm breakup module. You can install using Node Package Manager (npm):
npm install breakup
breakup.js is also available via bower. You can install using:
bower install breakup.js
breakup.js can be used as a drop-in replacement for the async.forEachSeries()
function in the
async.js Node.js module (which can also be used in browsers).
For example, instead of using async.forEachSeries()
:
var async = require('async');
async.forEachSeries(objs, function(i, item, callback) {}, function(err) {});
You can use breakup.js's version:
var breakup = require('breakup');
breakup.forEachSeries(objs, function(i, item, callback) {}, function(err) {});
Additional parameters are available for breakup.forEachSeries()
to control
the yielding behavior, see the Documentation for details.
To use any of the breakup.js functions, simply add a <script>
tag:
<script type="text/javascript" src="breakup.js"></script>
<script type="text/javascript">
breakup.forEachSeries(data, iterateFn, function(err){
// done
});
</script>
breakup.js can be used as a replacement for jQuery's jQuery.each()
as breakup.each()
,
or as a replacement for the jQuery selector jQuery(selector).each()
as jQuery(selector).breakup()
.
However, breakup.js may require some changes to existing jQuery code so it will
know how to handle the asynchronous nature of breakup.js. For example, when
you're using jQuery.each()
, the operation will block until the enumeration
is complete. Since breakup.js relies on callback events so it can yield to the browser,
existing jQuery code will need to pass in a callback-complete function parameter so it knows
when the enumeration has completed. If you don't do this, the code that follows may
break on the assumption that all of the enumeration in jQuery.each()
has
completed. Essentially, you will need to change your code to handle callback-driven
flow control.
In addition, this type of change may require you to change any code calling
the function that the original jQuery.each()
call was in if it returned a value
that depended on that work, as the new breakup.each()
's callback-complete function is
what will drive the new code flow. If you need the return value of a function that is calling
jQuery.each()
you will have to have breakup.each()
's callback-complete fire a
new callback with the return values instead of simply returning it in the original function call.
An example may help illustrate this better. You may be using jQuery.each()
like this:
function doIteration() {
var a = [];
$.each(
objs,
function(i, item) { a.push(item.something()); });
return a.length;
}
var b = doIteration();
// 'b' has your work
Here's how you should adjust the above code for breakup.each()
:
jQuery.each()
(or $.each()
) to breakup.each()
breakup.each()
, which is the callback-complete function$.each()
into
your new callback-complete function (eg return a.length
above)Sample:
function doIteration(callback) {
var a = [];
breakup.each(
objs,
function(i, item) { a.push(item.something()); },
function(err) {
callback(a.length);
});
}
doIteration(function(b) {
// 'b' has your work
// the code calling this might need to be changed to be callback-driven as well
});
This function should be a drop-in replacement for async.forEachSeries()
.
Applies an iterator function to each item in an array, in series. The iterator is called with an item from the list and a callback for when it has finished. If the iterator passes an error to this callback, the main callback for the forEachSeries function is immediately called with the error.
Arguments
arr
- An array to iterate over.iterator(item, callback)
- A function to apply to each item in the array.
The iterator is passed a callback(err)
which must be called once it has completed.
If no error has occurred, the callback should be run without arguments or
with an explicit null argument.callback(err)
- A callback which is called after all the iterator functions
have finished, or an error has occurred.workTime
- Work for this many milliseconds before yielding (optional, defaults
to breakup.DEFAULT_WORK_TIME
).yieldTime
- Time (in milliseconds) to delay during yielding, if setImmediate is
not available (optional, defaults to breakup.DEFAULT_YIELD_TIME
).forceYield
- Force yielding for the specified milliseconds instead of setImmediate/nextTick (optional).Example
// Node.js
var breakup = require('breakup');
var arr = [];
breakup.forEachSeries(
[1,2,3],
function(item, callback) {
arr.push(item);
callback();
},
function(err) {
}
);
You may set the yieldTime
and forceYield
parameters in a Node.js environment to force a yield of the
specified time instead of using Node's process.nextTick()
.
This function is meant to be a replacement for jQuery.each()
or jQuery(selector).each()
.
jQuery.each()
can be replaced by breakup.each()
(per NOTE below).
If jQuery is defined before breakup.js is included, jQuery will also be extended by
adding the jQuery(selector).breakup()
function.
each()
applies an iterator function to each item in an array, in series.
The iterator is called with the item's index, the item, and a callback for when it
has finished. If the iterator passes an error to this callback, the main
callback for the each function is immediately called with the error.
Arguments
arr
- An array to iterate over.iterator(index, item, callback)
- A function to apply to each item in the array.
The iterator is passed a callback(err)
which must be called once it has completed.
If no error has occured, the callback should be run without arguments or
with an explicit null argument.callback(err)
- A callback which is called after all the iterator functions
have finished, or an error has occurred.workTime
- Work for this many milliseconds before yielding (optional, defaults
to breakup.DEFAULT_WORK_TIME
).yieldTime
- Time (in milliseconds) to delay during yielding, if setImmediate is
not available (optional, defaults to breakup.DEFAULT_YIELD_TIME
).forceYield
- Force yielding for the specified milliseconds instead of setImmediate/nextTick (optional).Example
var arr = [];
breakup.each(
[1,2,3],
function(index, item, callback) {
arr.push(item);
callback();
},
function(err) {
}
);
// OR
$([1,2,3]).breakup(
function(index, item, callback) {
arr.push(item);
callback();
},
function(err) {
}
);
**NOTE**: The difference between `breakup.forEachSeries()` and `breakup.each()`
is the iterator signature: `forEachSeries()` iterates with `function(item, callback)` and
requires the callback to indicate work is done. This matches the `async.forEachSeries()`
signature. On the other hand, `each()` matches the jQuery signature by using the iterator
`function(index, item, callback)`, and waiting on the return of the function to move to the next
item. If you need to switch from `jQuery.each()` to `breakup.forEachSeries()`, you will need
to change the signature, and thus your code flow, to handle the callback instead of the function return.
Default for how many milliseconds to enumerate for prior to yielding.
By default, this value is set to 50
(ms).
If not specified as the fourth parameter of forEachSeries()
or each()
, this value will be used.
You may overwrite this value to change the global default.
This will be a best-case scenario. If a single item takes longer than the DEFAULT_WORK_TIME
to process, the yield won't occur until after that item fires its callback. In other words,
enumeration won't yield mid-item: the time-check is performed only at each item's callback.
How much time to yield for if process.nextTick()
or setImmediate()
is not available.
By default, this value is set to 4
(ms).
If not specified as the fifth parameter of forEachSeries()
or each()
, this value will be used.
You may overwrite this value to change the global default.
Changes the value of breakup back to its original value, returning a reference to the breakup object.
Tests are located under the test/
directory.
You can run them in three ways:
test/index.html
in your browsergrunt mochaTest
, which runs the tests using Mocha in the NodeJS consolegrunt karma
, which runs the tests using Mocha/Karma in a headless PhantomJS instancegrunt test
runs #2 and #3.
forceYield
parameterforceYield
parameterThis module (and documentation, tests, etc) were inspired by caolan's async.js module.
FAQs
Yielding enumeration replacement functions for async.forEachSeries() and jQuery.each()
We found that breakup demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PEP 770 proposes adding SBOM support to Python packages to improve transparency and catch hidden non-Python dependencies that security tools often miss.
Security News
Socket CEO Feross Aboukhadijeh discusses open source security challenges, including zero-day attacks and supply chain risks, on the Cyber Security Council podcast.
Security News
Research
Socket researchers uncover how threat actors weaponize Out-of-Band Application Security Testing (OAST) techniques across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.