What Really Happened with the is-promise NPM Incident? A Deep Dive
The article investigates the April 25 NPM outage caused by the is-promise package, explains the buggy code and the problematic "type" and "exports" fields in package.json, reviews the rapid fix timeline, and offers practical lessons for developers to avoid similar dependency mishaps.
Qi Yunlei, front‑end engineer at WeDoctor cloud services team, focuses on Node.js ecosystem construction and web‑app solutions.
Preface
On April 25 the NPM community erupted over an update incident triggered by a package called is-promise.
Rumors claimed that a single‑line package had impacted major projects from Google, Facebook, Amazon and caused chaos across the JavaScript ecosystem.
However, the author did not feel any real disruption and wondered whether such a small, rarely used package could truly be that destructive.
Motivated by curiosity and skepticism, the author decided to investigate.
is-promise Overview
First, let’s look at the complete source code of is-promise version 2.1.0 before the incident.
module.exports = isPromise;
function isPromise(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}This is a fairly permissive "Promise‑like" checker; despite the name, it behaves more like an is‑thenable utility. Writing such concise logic correctly requires solid expertise.
For example, the preceding typeof check can filter out malformed thenable strings like String.prototype.then = function () {}.
We should not dismiss the package’s value. The Promise/A+ spec is a flexible standard, not a language feature, and many implementations adopt an inclusive check like this.
Similar packages, such as Sindre Sorhus’s p-is-promise, also add a catch method check.
Review
Let’s revisit the timeline of the incident.
Timeline
Author Forbes Lindesay summarized the main events:
2020‑04‑25T15:03:25Z — Published problematic 2.2.0
2020‑04‑25T17:16:00Z — Ryan Zimmerman submitted a fix PR
2020‑04‑25T17:48:00Z — Alert received on social media
2020‑04‑25T17:54:00Z — Merged Ryan’s PR, released 2.2.1
2020‑04‑25T17:57:00Z — Closed related issues, opened a new thread for discussion
2020‑04‑25T18:06:00Z — Jordan Harband noted remaining "exports" field issue
2020‑04‑25T18:08:08Z — Removed "exports" from package.json, released 2.2.2
2020‑04‑25T19:20:00Z — Reverted 2.2.0 and 2.2.1
The author’s reaction was swift, but the delayed revert was still problematic.
Now we analyze each 2.2.x version.
2.2.0
Added TypeScript declaration file
Supported ES‑module style import
From a “god‑view” we see the root cause: two new fields were added to package.json:
{
"type": "module",
"exports": {
"import": "index.mjs",
"require": "index.js"
}
}Two types of errors quickly surfaced.
Error 1: the "exports" file paths omitted the required "./" prefix, causing Node.js to throw:
Error [ERR_INVALID_PACKAGE_TARGET]: Invalid "exports" main target "index.js" defined in the package config ...; targets must start with "./"Error 2: setting type: "module" disabled require, forcing imports to use ES modules:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: .../is-promise/index.jsA hidden third error was the missing update of the files field, so index.mjs and index.d.ts were not packaged.
2.2.1
Fixed the incorrect ESM usage
The corrected package.json looks like:
{
"exports": {
"import": "./index.mjs",
"require": "./index.js"
}
}However, requiring other files via require('is-promise/package.json') now throws:
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './package.json' is not defined by "exports"Even imports like require('is-promise/index') are blocked.
2.2.2
Removed the exports field from package.json This eliminated the breaking change introduced in 2.2.0.
Problematic Fields Explained
The incident stemmed from two relatively obscure package.json fields: type and exports.
type
The type field determines which module system the files in the directory follow. It can be:
commonjs : .js and .cjs follow CommonJS, .mjs follows ESM.
module : .js and .mjs follow ESM, .cjs follows CommonJS.
In early Node.js 12 versions the feature required the --experimental-modules flag; it became stable in Node 13.2.0. The author set type: "module", which broke CommonJS users.
exports
The exports field, introduced as the proposal-pkg-exports, allows fine‑grained control over which files are exposed to different environments. It supplements the traditional main field.
When exports is defined, it overrides main and only the explicitly listed files can be imported.
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": {
"browser": "./feature-browser.js",
"default": "./feature.js"
}
}
}Node.js versions that support exports require the paths to start with "./"; omitting it leads to the earlier error.
Author’s Post‑Mortem
The author later published a post‑mortem outlining four main causes:
Local publishing without CI verification
Using new features without CI supporting the required Node version
Testing only the code, not the published package
Author unavailable, other maintainers lacked a way to publish a fix
In short: insufficient testing and an unsystematic release process.
Impact Assessment
Issues were found in projects like create‑react‑app, @angular/cli, and firebase‑tools, causing install or build failures.
The is-promise package has tens of millions of weekly downloads, with 766 direct dependents (now 561 after the incident) and over 3.5 million GitHub dependents.
The bug existed for roughly three hours, but NPM’s caching extended the real‑world impact.
While the reach was broad, the actual damage was not as dramatic as some rumors suggested.
Observer’s Reflections
How can we prevent similar tragedies?
Pin Versions
Locking dependencies eliminates this class of surprise, especially for application developers.
However, library authors should avoid over‑locking to keep receiving upstream fixes.
Unit Tests
Testing is essential. The is-promise change lacked any test coverage, even for basic require usage.
Small Libraries
Even tiny libraries carry maintenance overhead; developers should evaluate whether a dependency is truly needed.
Documentation
The type field’s impact was clear, but the exports field’s breaking nature was poorly documented, leading the author to underestimate its risk.
Package Managers
Both Yarn and NPM have converged in features; mixing them is discouraged.
Yarn’s speed advantage is no longer significant.
Tools like PNPM that aim to improve the NPM ecosystem are worth watching.
Conclusion
Critics of the NPM ecosystem often do not contribute to its improvement; constructive voices are rare.
Users need not panic—human error is inevitable, but disciplined processes can greatly reduce negative impact.
Sometimes harsh advice is valuable; we hope more useful perspectives emerge.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
WeDoctor Frontend Technology
Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
