{"id":"2ec0eb23536b3b62","repo":"jquery/jquery","slug":"thenable-self-resolution","errorCode":null,"errorMessage":"Thenable self-resolution","messagePattern":"Thenable self-resolution","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/deferred.js","lineNumber":123,"sourceCode":"\t\t\t\t\t\treturn function() {\n\t\t\t\t\t\t\tvar that = this,\n\t\t\t\t\t\t\t\targs = arguments,\n\t\t\t\t\t\t\t\tmightThrow = function() {\n\t\t\t\t\t\t\t\t\tvar returned, then;\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.3\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-59\n\t\t\t\t\t\t\t\t\t// Ignore double-resolution attempts\n\t\t\t\t\t\t\t\t\tif ( depth < maxDepth ) {\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturned = handler.apply( that, args );\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.1\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-48\n\t\t\t\t\t\t\t\t\tif ( returned === deferred.promise() ) {\n\t\t\t\t\t\t\t\t\t\tthrow new TypeError( \"Thenable self-resolution\" );\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ sections 2.3.3.1, 3.5\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-54\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-75\n\t\t\t\t\t\t\t\t\t// Retrieve `then` only once\n\t\t\t\t\t\t\t\t\tthen = returned &&\n\n\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.4\n\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-64\n\t\t\t\t\t\t\t\t\t\t// Only check objects and functions for thenability\n\t\t\t\t\t\t\t\t\t\t( typeof returned === \"object\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof returned === \"function\" ) &&\n\t\t\t\t\t\t\t\t\t\treturned.then;\n\n\t\t\t\t\t\t\t\t\t// Handle a returned thenable\n\t\t\t\t\t\t\t\t\tif ( typeof then === \"function\" ) {\n","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/jquery/jquery/blob/51eb576cca6ffce252dc152bf8e91d5230a0d887/src/deferred.js#L105-L141","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar d = $.Deferred();\nd.then( function( v ) {\n\tdoWork( v );\n\treturn d.promise(); // self-resolution -> TypeError\n} );\nd.resolve( 42 );\n\n// after\nvar d = $.Deferred();\nd.then( function( v ) {\n\tdoWork( v );\n\treturn v + 1; // return a value\n} );\nd.resolve( 42 );","handlingStrategy":"validation","validationCode":"// Validate that a handler does not return the parent deferred's promise\nfunction attachSafe( deferred, handler ) {\n\tvar parentPromise = deferred.promise();\n\tdeferred.then( function() {\n\t\tvar ret = handler.apply( this, arguments );\n\t\tif ( ret === parentPromise ) {\n\t\t\tconsole.warn( \"handler returned its own deferred promise; ignoring\" );\n\t\t\treturn undefined;\n\t\t}\n\t\treturn ret;\n\t} );\n}","typeGuard":"// Guard: detect a returned value that is the same Deferred's promise\nfunction isSelfResolution( returned, parentPromise ) {\n\treturn returned === parentPromise;\n}\n// usage inside .then handler:\n// if ( isSelfResolution( value, deferred.promise() ) ) return;","tryCatchPattern":"var d = $.Deferred();\nd.then( function() {\n\treturn maybeSamePromise( d.promise() );\n} ).catch( function( err ) {\n\tif ( err instanceof TypeError && /self-resolution/i.test( err.message ) ) {\n\t\tconsole.error( \"Deferred self-resolution detected; fix the handler.\" );\n\t\treturn; // recover\n\t}\n\tthrow err;\n} );\nd.resolve();","preventionTips":["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."],"tags":["promises","promises-aplus","jquery-deferred","async","self-resolution","deadlock"],"analyzedSha":"51eb576cca6ffce252dc152bf8e91d5230a0d887","analyzedAt":"2026-08-03T20:30:50.652Z","schemaVersion":2}