chiffchaff

0.2.0 • Public • Published

chiffchaff

npm Dependencies Build Status Coverage Status JavaScript Standard Style

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

Tim De Pauw

License

MIT

Package Sidebar

Install

npm i chiffchaff

Weekly Downloads

11

Version

0.2.0

License

MIT

Last publish

Collaborators

  • delbeke
  • timdp