lie vs bluebird vs q vs rsvp vs when
Evaluating Legacy Promise Libraries for Modern Architectures
liebluebirdqrsvpwhenSimilar Packages:

Evaluating Legacy Promise Libraries for Modern Architectures

These five libraries represent the pre-ES6 era of asynchronous JavaScript, offering Promise implementations and extensions before native support arrived. bluebird, q, when, and rsvp provided feature-rich APIs for complex async flows, while lie focused on being a lightweight polyfill. In modern development, native Promises and async/await have replaced most of these tools, leaving them primarily relevant for maintaining legacy systems or specific polyfill needs.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
lie67,312,343741-59 years agoMIT
bluebird020,470-1307 years agoMIT
q014,998-115-MIT
rsvp03,591-177 years agoMIT
when03,420-6710 years agoMIT

Legacy Promise Libraries: Technical Deep Dive and Migration Guide

Before ES6 introduced native Promises, developers relied on third-party libraries to handle asynchronous control flow. Packages like bluebird, q, rsvp, when, and lie filled this gap with varying approaches to API design, performance, and feature sets. Today, understanding these tools is less about choosing one for new work and more about maintaining legacy systems or understanding the history of async JavaScript. Let's examine how they differ in practice.

⚠️ Critical Maintenance Status

Before diving into code, you must know the current state of these packages. Most are no longer suitable for new development.

  • q: Officially deprecated. The repository is archived.
  • when: Repository archived. No longer maintained.
  • rsvp: Legacy status. Primarily tied to older Ember.js versions.
  • bluebird: Maintenance mode. Feature-complete but native Promises are preferred.
  • lie: Polyfill only. Useful only for very old environments.

πŸ’‘ Rule of Thumb: If you are starting a new project, use native Promises. Only include these packages if you are forced to support old code or browsers without ES6.

πŸ”„ Concurrency and Mapping

One of the biggest advantages these libraries offered over early native Promises was helper methods for running multiple tasks. Native Promises now have Promise.all and Promise.allSettled, but libraries like bluebird and when offered mapped concurrency.

bluebird provides a powerful Promise.map that limits concurrency.

const Promise = require('bluebird');

const urls = ['/api/1', '/api/2', '/api/3'];

// Concurrency limit of 2
Promise.map(urls, fetchUrl, { concurrency: 2 })
  .then(results => console.log(results));

q relies on Q.all for parallel execution but lacks built-in concurrency limiting.

const Q = require('q');

const tasks = [fetchUrl('/api/1'), fetchUrl('/api/2')];

Q.all(tasks)
  .then(results => console.log(results));

when offers when.map similar to Bluebird.

const when = require('when');

const urls = ['/api/1', '/api/2'];

when.map(urls, fetchUrl)
  .then(results => console.log(results));

rsvp uses RSVP.all for parallel execution.

const RSVP = require('rsvp');

const tasks = [fetchUrl('/api/1'), fetchUrl('/api/2')];

RSVP.all(tasks)
  .then(results => console.log(results));

lie implements the standard spec only. You must use native-style Promise.all.

require('lie'); // Polyfills global Promise

const tasks = [fetchUrl('/api/1'), fetchUrl('/api/2')];

Promise.all(tasks)
  .then(results => console.log(results));

πŸ›‘οΈ Error Handling and Propagation

Error handling strategies varied significantly. Some libraries encouraged explicit deferred objects, while others pushed for chaining.

bluebird supports robust error filtering and timeouts.

const Promise = require('bluebird');

Promise.timeoutOperation()
  .timeout(5000)
  .catch(Promise.TimeoutError, err => {
    console.log('Operation took too long');
  });

q uses .fail for error handling and often relies on deferred objects.

const Q = require('q');
const deferred = Q.defer();

// ... async work ...
// deferred.resolve(data);
// deferred.reject(error);

deferred.promise.fail(err => {
  console.error('Failed:', err);
});

when uses .otherwise for catching errors.

const when = require('when');

when.resolve().then(() => {
  throw new Error('Oops');
}).otherwise(err => {
  console.error('Caught:', err);
});

rsvp uses standard .catch similar to native Promises.

const RSVP = require('rsvp');

new RSVP.Promise((resolve, reject) => {
  // ... work ...
}).catch(err => {
  console.error('Error:', err);
});

lie follows the standard spec exactly.

require('lie');

new Promise((resolve, reject) => {
  // ... work ...
}).catch(err => {
  console.error('Error:', err);
});

πŸ“¦ Polyfill vs. Feature Extension

A key distinction is whether the package replaces the global Promise or adds a new object with extra tools.

bluebird replaces or extends the global Promise with many extra methods.

const Promise = require('bluebird');
// Adds methods like .delay, .cancel, .map to Promise prototype
Promise.delay(1000).then(() => console.log('Done'));

q creates a separate Q object, leaving global Promise untouched.

const Q = require('q');
// Uses Q object explicitly
Q.fcall(() => 123).then(console.log);

when exports a function and namespace for utilities.

const when = require('when');
// Uses when() function or when.promise()
when.resolve(123).then(console.log);

rsvp exports an RSVP namespace.

const RSVP = require('rsvp');
// Uses RSVP.Promise
new RSVP.Promise(resolve => resolve(123));

lie Polyfills the global Promise constructor only.

require('lie');
// Just makes global Promise work in old IE
new Promise(resolve => resolve(123));

πŸ“Š Summary Table

Featurebluebirdlieqrsvpwhen
StatusMaintenancePolyfillDeprecatedLegacyArchived
Extra Utilsβœ… Extensive❌ Noneβœ… Moderateβœ… Someβœ… Moderate
Concurrencyβœ… map w/ limit❌ Native only❌ all only❌ all onlyβœ… map
Timeoutsβœ… Built-in❌ Manual❌ Manual❌ Manualβœ… Built-in
Global Polyfillβœ… Optionalβœ… Yes❌ No❌ No❌ No

πŸ’‘ Final Recommendation

The landscape of asynchronous JavaScript has stabilized around the ES6 standard. For any new architecture, native Promises and async/await are the correct choice. They are built into the runtime, require no dependencies, and are optimized by engine vendors.

bluebird remains the only viable option if you need advanced features like cancellation or concurrency limiting in a Node.js environment, but even then, consider modern alternatives like p-limit. lie has a narrow use case for polyfilling very old browsers without a build pipeline. q, rsvp, and when should be treated as legacy debt β€” maintain them if you must, but plan their removal.

Bottom Line: Do not introduce these dependencies into new codebases. Use native Promises for clarity, performance, and long-term maintainability.

How to Choose: lie vs bluebird vs q vs rsvp vs when

  • lie:

    Choose lie if you need a minimal Promise polyfill for very old browsers that do not support ES6 and you cannot use a build step to transpile code. It is strictly spec-compliant without extra features, making it safe for environments where bundle size is critical. Do not use it in modern environments where native Promises are available.

  • bluebird:

    Choose bluebird only if you are maintaining a legacy Node.js codebase that relies on its specific extensions like cancellation, timeouts, or resource mapping. It is the most feature-complete option among the group, but new projects should use native Promises instead. Avoid adding it as a dependency for greenfield development due to the maintenance overhead.

  • q:

    Do not choose q for any new work as it is officially deprecated and no longer maintained. Only interact with this library if you are refactoring an older codebase that heavily depends on its deferred pattern or progress callbacks. Plan to migrate away from it to native Promises as soon as possible to reduce technical debt.

  • rsvp:

    Choose rsvp only if you are working within an older Ember.js application that has not yet migrated to native Promises. It offers a solid implementation with some helpers like hash, but it is considered legacy technology in the broader ecosystem. New projects should avoid it in favor of standard JavaScript features.

  • when:

    Do not choose when for new projects as the repository is archived and the package is no longer updated. It was once known for performance, but native engines have surpassed it, and it lacks modern safety guarantees. Use it only when maintaining specific legacy integrations that cannot be easily rewritten.

README for lie

lie

Promises/A+ logo [![Build Status](https://travis-ci.org/calvinmetcalf/lie.svg)](https://travis-ci.org/calvinmetcalf/lie)

lie is a small, performant promise library implementing the Promises/A+ spec (Version 1.1).

Originally a fork of Ruben Verborgh's promiscuous, with version 2.6 it became a fork of ayepromise by Chris Burgmer.

npm install lie

var Promise = require('lie');
// or use the pollyfill
require('lie/polyfill');

Usage

Either use it with browserify (recommended) or grab one of the files from the dist folder:

  • lie.js/lie.min.js exposes 'Promise' either as a UMD module or from the global scope, depending on if a CJS or AMD loader is available.
  • lie.polyfill.js/lie.polyfill.min.js adds 'Promise' to the global scope only if it's not already defined (not a UMD).

API

Implements the standard ES6 api:

new Promise(function(resolve, reject){
    doSomething(function(err, result) {
        if (err) {
            reject(err);
        } else {
            resolve(result);
        }
    });
}).then(function (value) {
    //on success
}, function (reason) {
    //on error
}).catch(function (reason) {
    //shortcut for error handling
});

Promise.all([
    //array of promises or values
]).then(function ([/* array of results */]));

Promise.race([
    //array of promises or values
]);
// either resolves or rejects depending on the first value to do so

Unhandled Rejections

In Node.js, lie emits an unhandledRejection event when a rejected promise isn't caught, in line with how io.js does it. This allows it to act as a promise shim in both Node.js and the browser.