jquery/jquery · error · TypeError
Thenable self-resolution
Error message
Thenable self-resolution
What it means
Raised in src/deferred.js:122-124 inside the Deferred `.then` resolution machinery. It enforces Promises/A+ section 2.3.1 (https://promisesaplus.com/#point-48): a promise cannot be resolved with itself. When an onFulfilled/onRejected handler returns exactly the same Deferred's own promise object (returned === deferred.promise()), jQuery throws a TypeError instead of entering an infinite resolution loop. The guard is deliberate — without it the chain would recurse forever.
Source
Thrown at src/deferred.js:123
return function() {
var that = this,
args = arguments,
mightThrow = function() {
var returned, then;
// Support: Promises/A+ section 2.3.3.3.3
// https://promisesaplus.com/#point-59
// Ignore double-resolution attempts
if ( depth < maxDepth ) {
return;
}
returned = handler.apply( that, args );
// Support: Promises/A+ section 2.3.1
// https://promisesaplus.com/#point-48
if ( returned === deferred.promise() ) {
throw new TypeError( "Thenable self-resolution" );
}
// Support: Promises/A+ sections 2.3.3.1, 3.5
// https://promisesaplus.com/#point-54
// https://promisesaplus.com/#point-75
// Retrieve `then` only once
then = returned &&
// Support: Promises/A+ section 2.3.4
// https://promisesaplus.com/#point-64
// Only check objects and functions for thenability
( typeof returned === "object" ||
typeof returned === "function" ) &&
returned.then;
// Handle a returned thenable
if ( typeof then === "function" ) {
View on GitHub (pinned to 51eb576cca)
Solutions
- Do not return the same Deferred's promise from its own .then() handler; return a value, a new Deferred/promise, or nothing.
- If you need to gate on the same work, create a fresh Deferred and resolve it separately: var d2 = $.Deferred(); d.then(function(){ d2.resolve(...); }); return d2.promise();
- Restructure so the handler returns the actual resolved value rather than the promise object.
- Use native Promise if you want the (still incorrect but non-throwing) self-resolution behavior, but prefer fixing the cycle.
- Add a unit test asserting handlers never return the parent deferred to catch regressions.
Example fix
// before
var d = $.Deferred();
d.then( function( v ) {
doWork( v );
return d.promise(); // self-resolution -> TypeError
} );
d.resolve( 42 );
// after
var d = $.Deferred();
d.then( function( v ) {
doWork( v );
return v + 1; // return a value
} );
d.resolve( 42 ); Defensive patterns
Strategy: validation
Validate before calling
// Validate that a handler does not return the parent deferred's promise
function attachSafe( deferred, handler ) {
var parentPromise = deferred.promise();
deferred.then( function() {
var ret = handler.apply( this, arguments );
if ( ret === parentPromise ) {
console.warn( "handler returned its own deferred promise; ignoring" );
return undefined;
}
return ret;
} );
} Type guard
// Guard: detect a returned value that is the same Deferred's promise
function isSelfResolution( returned, parentPromise ) {
return returned === parentPromise;
}
// usage inside .then handler:
// if ( isSelfResolution( value, deferred.promise() ) ) return; Try / catch
var d = $.Deferred();
d.then( function() {
return maybeSamePromise( d.promise() );
} ).catch( function( err ) {
if ( err instanceof TypeError && /self-resolution/i.test( err.message ) ) {
console.error( "Deferred self-resolution detected; fix the handler." );
return; // recover
}
throw err;
} );
d.resolve(); Prevention
- Never return the outer Deferred's promise from inside its own .then() handler.
- When chaining, create a fresh Deferred/promise to return instead of reusing the same one.
- Add unit tests asserting handlers return values or NEW promises, never the parent.
- If migrating from native Promise, audit each handler's return for identity cycles.
- Treat this TypeError as a logic bug to refactor, not an exception to swallow.
When it happens
Trigger: Calling `.then()` (or `.done`/`.pipe` paths that flow through the same resolver) and returning the parent Deferred's own promise from the handler: `var d = $.Deferred(); d.then( function() { return d.promise(); } ); d.resolve();`. Also reached when a handler returns `this` where `this` happens to be the deferred's promise, or by aliasing the promise into a returned variable. The check is strict identity (===) against deferred.promise(), so only the exact same object triggers it.
Common situations: Refactoring async code and accidentally returning the outer promise from an inner handler; chaining a Deferred into itself to 'wait for completion'; libraries that hand out the same promise to both producer and consumer callbacks; migrating from native Promise (which silently schedules an infinite loop) to jQuery Deferred and discovering the stricter guard.
AI-assisted analysis of jquery/jquery@51eb576cca (2026-08-03).
Data as JSON: /data/errors/2ec0eb23536b3b62.json.
Report an issue: GitHub.