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

  1. Do not return the same Deferred's promise from its own .then() handler; return a value, a new Deferred/promise, or nothing.
  2. 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();
  3. Restructure so the handler returns the actual resolved value rather than the promise object.
  4. Use native Promise if you want the (still incorrect but non-throwing) self-resolution behavior, but prefer fixing the cycle.
  5. 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

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.