Last active
August 29, 2015 14:11
-
-
Save heyimalex/944bb7d40f96717573cf to your computer and use it in GitHub Desktop.
Asynchronous concurreny control in Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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