Skip to content

Instantly share code, notes, and snippets.

@heyimalex
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heyimalex/944bb7d40f96717573cf to your computer and use it in GitHub Desktop.
Save heyimalex/944bb7d40f96717573cf to your computer and use it in GitHub Desktop.
Asynchronous concurreny control in Javascript
/**
* For limiting concurrency, Javascript style.
*
* Created for a document viewer; basically a number of thumbnails and
* a full image of the current page. "Changing the page" entailed
* changing the src of the full image, and the expectation was for that
* page to load. However, the thumbnails would saturate the browser's
* download limit, so the page wouldn't change until _all_ of the
* thumbnails had finished downloading. With 300+ page documents and
* thumbnails generated dynamically on the server, obviously that was
* a little to slow.
*
* With the semaphore, I could limit concurrency below the download
* limit by:
* - creating an img with no src
* - setting the src when the semaphore was acquired
* - releasing the semaphore onload and onerror
*
* That's a simplification, but it gets the point across. I included an
* example similar to what I ended up doing.
*
*/
function Semaphore(max) {
if (!(this instanceof Semaphore)) {
return new Semaphore(max);
}
var queue = [];
var counter = 0;
this.acquire = function(fn) {
if (counter < max) {
counter++;
dispatch(fn);
}
else {
queue.push(fn);
}
};
var release = function() {
var next = queue.shift();
if (next === undefined) {
counter--;
}
else {
dispatch(next);
}
};
var dispatch = function(fn) {
// Pass dispatched function a callback that releases the
// semaphore, and ensure that it can only be called once.
var executed = false;
fn(function() {
if (executed === false) {
executed = true;
release();
}
});
};
}
module.exports = Semaphore;
// Example
var loadSemaphore = new Semaphore(4);
var Thumbnail = React.createClass({
getInitialState: function() {
return { load: false };
},
componentDidMount: function() {
loadSemaphore.acquire(this.acquire);
},
componentWillUnmount: function() {
this.release();
},
acquire: function(release) {
if (!this.isMounted()) {
release();
return;
}
this.setState({ load: true });
this.release = release;
},
release: function() {}, // no-op
render: function() {
var props = {
src: this.state.load?'some/image.jpg':''
onLoad: this.release,
onError: this.release
};
return <img {...props}>;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment