To use breathe.js, you split code into smaller chunks that don't have to get executed all at once. For instance, in traditional JavaScript, you may have a long looping function:
function longLoopingFunction() {
var i;
for(i = 0; i < 100000; i++) {
trickyFunction();
}
}
Here trickyFunction()
runs 100,000 times, without letting any other code run or UI respond. But with breathe.js, the same code can be written as:
function breathableLongLoopingFunction() {
return breathe.times(100000, function (i) {
trickyFunction();
});
}
Here the function also runs sequentially, but if it runs for too long (over 17 milliseconds by default), breathe.js relinquishes the main thread to allow other functions to run or UI to respond, then runs the remaining loop, repeatedly relinquishing if necessary.
Functions using breathe.js
create breathable chains. Breathable chains don't immediately finish and therefor use asynchronous conventions of then
and catch
to run code and catch errors sequentially. It's very similar to a promise's control flow, with some deviations. To run code after a breathable function finishes, you can use the then
method, like so:
breathableLongLoopingFunction().then(function () {
console.log('All finished!');
});
Likewise, you can chain breathable code together within functions using the then
method:
function breathableSequentialLongLoopingFunction() {
return breathe.times(100000, function (i) {
trickyFunction(); // run 100000 times
}).then(function () {
// then run this loop
return breathe.times(300000, function (j) {
anotherTrickyFunction(); // run 300000 times
});
}).then(function () {
console.log('All done!');
});
}
You can create breathable chains via the breathe.chain()
constructor. Consider this code that has a sequence of computationally-intensive functions:
function longRunningFunction() {
easyFunction();
trickyFunction();
difficultFunction();
// ...
almostImpossibleFunction();
}
This can be split into smaller bits of breathable code, with a breathable chain:
function breathableLongRunningFunction() {
return breathe.chain() // creates a breathable chain
.then(function() {
easyFunction();
trickyFunction();
}).then(function() {
difficultFunction();
}) //...
.then(function() {
almostImpossibleFunction();
});
}
In this case, functions will run in sequential order, but if they took too long to run (over 17 milliseconds total, by default), breathe will relinquish the main thread to other code, before automatically reentering to where it left off.
This code may still block the main thread if the individual subfunctions (e.g. almostImpossibleFunction
) take a long time to execute. To alleviate this, functions can be further subdivided into breathable code; for instance trickyFunction
, difficultFunction
, and almostImpossibleFunction
may be converted to breathable functions, like longRunningFunction
was converted to breathableLongRunningFunction
. In that case the invoking syntax slightly changes:
function breathableLongRunningFunction() {
return breathe.chain()
.then(function () {
easyFunction();
return breathableTrickyFunction(); // note the 'return'
}).then(function () {
return breathableDifficultFunction();
}) // maybe have more functions/code ...
.then(function () {
return breathableAlmostImpossibleFunction();
});
}
This allows for the subfunctions and code not to hog the main thread; if they take too long to execute, they will relinquish the main thread to other code.
In JavaScript, functions declared inside other functions can access the outer function's variables. This is particularly useful with breathe.js's extensive use of inner functions. If multiple functions are sharing a variable, make sure the variable is declared in a context shared by both variables.
function breathableMultiLegFunction() {
var a = 2, b = 3;
return breathe.chain().then(function () {
var c = 4;
// can access a, b, and c;
b = a + 5;
}).then(function () {
// can access only a and b;
console.log(b); // 7
});
}
In Promise conventions, the results of asynchronous code can be passed via a return
statement in a then
method. That result is then passed to the callback function in the next then
method. breathe.js follows these conventions:
function breathableAddFunction(a, b) {
return breathe.chain().then(function() {
return a + b; // note the return is within a .then method
});
}
// invocation:
breathableAddFunction(10, 12)
.then(function (result) {
console.log(result); // 22
});
In cases where multiple breathable functions are called, with values returned, shared variables can be used to store their results for further computation.
function breathableAddFourFunction (a, b, c, d) {
var aPlusB, cPlusD;
return breathe.chain().then(function () {
return breathableAddFunction(a, b);
}).then(function (result) {
aPlusB = result;
}).then(function () {
return breathableAddFunction(c, d);
}).then(function (result) {
cPlusD = result;
return aPlusB + cPlusD;
});
}
This can be compacted by assigning results and calling the next breathable function in the same .then
statement, though this convention may be a little more confusing.
function compactAddFourFunction (a, b, c, d) {
var aPlusB;
return breathe.chain().then(function () {
return breathableAddFunction(a, b);
}).then(function (result) {
aPlusB = result;
return breathableAddFunction(c, d);
}).then(function (result) {
return aPlusB + result;
});
}
breathe.times
loops are useful, but they replace only certain types of for
loops.breathe.loop
on the other hand, offers much more flexibility, similar to a while loop.
For example, consider this function:
function longWhileFunction() {
var i = 0;
while (++i < 2000 && condition()) {
difficultFunction();
}
}
Since anonymous functions can access non-local variables, it can be rewritten with breathe.js, simply as:
function breathableLongWhileFunction() {
var i = 0;
return breathe.loop(function() { return ++i < 2000 && condition(); }, function() {
difficultFunction();
});
}
The second argument for breathe.loop
and breathe.times
is the loop body; if it returns a promise or a breathable chain, it will wait until that code finishes before looping. This allows you to nest loops:
function breathableNestedFunction() {
return breathe.times(100000, function (i) {
return breathe.times(100000, function (j) {
multidimensionalFunction(i, j);
});
});
}
In general, functions can be subdivided into blocks of code, with variable declarations, synchronous code, and/or asynchronous code. The general pattern is:
function () {
var variablesSharedInsideOfThisBlock;
synchronousCode();
return asynchronousCode();
}
You don't need to have both synchronous code or asynchronous code, though breathable code often involves subsequent code blocks. For instance, breathe.chain
has a sequence of code blocks in then
methods.
function breathableLongRunningFunction() {
// this is a code block
return breathe.chain() // asynchronous code
.then(function () {
// this is another code block
easyFunction(); // synchronous code
return breathableTrickyFunction(); // asynchronous
}).then(function () {
// yet another code block
return breathableDifficultFunction(); // asynchronous
}) // maybe have more functions/code ...
.then(function () {
// even another code block
return breathableAlmostImpossibleFunction(); // asynchronous
});
}
Likewise, breathe.loop
takes a body
as an argument, which is a code block:
function nestedLoop() {
// one code block
var running;
running = true;
return breathe.loop(function () { return running; }, function () {
// another code block
var i; // declaration
i = 0; // synchronous code
return breathe.loop(function () { return running && i++ < 50; },
function () {
// yet another code block
running = doSomethingAwesome(c);
}
);
});
}
If variables' values are shared by multiple blocks of code, their declaration should be moved to a context accessible to those blocks of code.
function sequentialLoops() {
var a, b, c; // variables to declare, shared by multiple blocks of code
a = computeSomething(); // synchronous code
return breathe.chain() // return signifies the start of asynchronous code
.then(function () {
// another block of code
c = a; // synchronous code
return breathe.loop({ // asynchronous code
condition: function () { return c > 0; }, // condition is always synchronous
body: function () { // yet another code block
var d;
d = c * a;
draw(d);
c--;
}
});
}).then(function () {
b = a + computeSomethingElse();
// if it doesn't return a promise (or anything at all),
// it's considered synchronous
}).then(function () {
return b; // return a value for sequentialLoops()
});
}
var chain = breathe.times(100000, function () {
hardWork();
});
pauseButton.addEventListener('click', function () {
chain.pause()
.then(function () {
console.log('Paused!')
});
});
unpauseButton.addEventListener('click', function () {
chain.unpause()
.then(function () {
console.log('Unpaused!')
});
});
stopButton.addEventListener('click', function () {
chain.stop()
.then(function () {
console.log('Stopped!')
});
});
console.log
statements or chunks that execute longer than expected, it can make the UI sluggish or nonresponsive. Without the warning, it can be more difficult to stop the page.It's usually straightforward converting synchronous code to breathable code. Usually code can be mapped one-to-one, preserving the order, structure, and logic of the code. Here are some examples of converting code:
function basicFunction() {
var i;
var j;
i = getAmount();
while(i++ < 200 && j !== 4) {
j = doOneThing();
}
doAnotherThing(j);
return yetAnotherThing(j);
}
function basicFunction() {
var i;
var j;
i = getAmount();
return breathe.loop(function () { return i++ < 200 && j !== 4;},
function () {
j = doOneThing();
}
).then(function () {
doAnotherThing(j);
return yetAnotherThing(j);
});
}
function sequentialLoops() {
var i;
var j;
i = getAmount();
while(i < 200 && j !== 4) {
j = doOneThing();
i++;
}
doAnotherThing(j);
i=0;
while(i < 200 && j > 4) {
j = doYetOneMoreThing();
i++;
}
return yetAnotherThing(j);
}
function sequentialLoops() {
var i;
var j;
i = getAmount();
return breathe.loop(function () { return i < 200 && j !== 4;},
function () {
j = doOneThing();
i++;
}
).then(function () {
doAnotherThing(j);
i=0;
}).then(function () {
return breathe.loop(function () { return i < 200 && j > 4; }, function() {
j = doYetOneMoreThing();
i++;
});
}).then(function () {
return yetAnotherThing(j);
});
}
function nestedLoops() {
var i;
var j;
var k;
i = getAmount();
while(i < 200 && isGood(j)) {
j = doOneThing();
k = 0;
while(k < 200 && j > 4) {
j = doYetOneMoreThing();
k++;
}
i++;
}
doAnotherThing(j);
return yetAnotherThing(j);
}
function nestedLoops() {
var i;
var j;
var k;
i = getAmount();
return breathe.loop(function () { return i < 200 && isGood(j);},
function () {
j = doOneThing();
k = 0;
return breathe.loop(function() { return k < 200 && j > 4; }, function(){
j = doYetOneMoreThing();
k++;
}).then(function(){
i++;
});
}
).then(function () {
doAnotherThing(j);
return yetAnotherThing(j);
});
}