chiffchaff v0.2.0
chiffchaff
Cancellable promises with progress reporting. A more object-oriented approach to using bluebird.
Example
Note: Like chiffchaff itself, this example is written in ES2015. Please familiarize yourself with ES2015 before tackling it.
Let's say we want to create a DownloadTask class that we can use to download
a file over HTTP. This would be a typical instantiation of that class:
const url = 'http://media.w3.org/2010/05/sintel/trailer.mp4'
const videoDownloadTask = new DownloadTask(url)By default, a chiffchaff Task will emit start, progress, and end events
whenever its status changes. Thus, we can listen for those, for example to log
progress information to the console.
videoDownloadTask
.on('start', () => console.info('Starting download ...'))
.on('progress',
(compl, total) => console.info('Progress: %d/%d bytes', compl, total))A Task will not carry out any work until its start function is called. That
one returns a
cancellable promise
which will be fulfilled with the result of the task. Hence, we start our
download task as follows:
videoDownloadTask.start()
.then(result => console.info('Download complete: %d bytes', result.length))
.catch(err => console.error('Error: %s', err))
.finally(() => {
if (videoDownloadTask.isCancelled()) {
console.warn('Download cancelled')
}
})Let's say that after a second, we change our mind and want to cancel the
download. It's as simple as calling cancel on the task.
setTimeout(() => {
console.info('Cancelling download ...')
videoDownloadTask.cancel()
}, 1000)Now that we've established the DownloadTask API, let's actually implement the
class. As you may already have guessed, it's essentially a wrapper for Node's
http.get.
To avoid having to enable bluebird's cancellation feature manually, chiffchaff
exports a preconfigured Promise alongside its own Task. You can also access
it as require('chiffchaff').Promise if you prefer CommonJS.
import {default as Task, Promise} from 'chiffchaff'
import http from 'http'
class DownloadTask extends Task {
constructor (url) {
super()
this._url = url
this._request = null
this._downloaded = 0
this._contentLength = 0
this._data = []
}
// Subclasses of Task must only override the _start function. It returns a
// cancellable promise for the work which the task is carrying out.
_start () {
return new Promise((resolve, reject, onCancel) => {
// Hold on to the callbacks so we can use them below.
this._resolve = resolve
this._reject = reject
this._request = http.get(this._url, res => this._onResponse(res))
.once('error', err => reject(err))
// If the task gets cancelled, abort the underlying HTTP request.
onCancel(() => this._request.abort())
})
}
_onResponse (response) {
if (response.statusCode !== 200) {
this._reject(new Error(`HTTP ${response.statusCode}`))
} else {
this._contentLength = parseInt(response.headers['content-length'], 10)
// Whenever a task has updated information on its progress, it should call
// _notify with two numbers: the completed amount and the total amount.
// In this case, we're passing the number of bytes downloaded and the
// total size of the file.
this._notify(this._downloaded, this._contentLength)
response
.on('data', chunk => this._onData(chunk))
.once('end', () => this._resolve(Buffer.concat(this._data)))
}
}
_onData (chunk) {
this._downloaded += chunk.length
this._data.push(chunk)
this._notify(this._downloaded, this._contentLength)
}
}A more robust implementation of DownloadTask will eventually be made available
as a Node module.
Maintainer
License
MIT