如何将jQuery Deferred替换为原生Promise?替换后Promise.all未触发
问题场景
按钮点击触发的handleClick函数中,原本使用jQuery Deferred时Promise.all能正常执行并输出show this,但将其中的Deferred替换为原生Promise后,Promise.all的后续回调不再触发。
原错误处理的jQuery Deferred片段:
return new $.Deferred() .resolve({ folderError: error, }) .promise();
替换后的原生Promise写法:
return new Promise((resolve)=>{ resolve({ folderError: error, }) })
问题原因
核心矛盾是jQuery Deferred对象与原生Promise的兼容性问题:api.cloneFolder(ad.id)返回的是jQuery Deferred对象,当在它的.then()回调中返回原生Promise时,jQuery的Deferred链无法正确识别并转换这个原生Promise的状态,导致Promise.all无法感知该异步操作已完成,最终卡住不触发后续回调。
另外,代码的default分支未返回任何值,会导致clonePromises数组中混入undefined,虽然原生Promise.all会将undefined视为已resolved,但混合jQuery Deferred后会加剧状态识别的混乱。
解决方案
方案1:将jQuery Deferred转换为原生Promise
在调用api.cloneFolder时用Promise.resolve()包裹,把jQuery的Deferred对象转换为原生Promise,确保整个异步链统一为原生Promise标准:case 'folder': return Promise.resolve(api.cloneFolder(ad.id)).catch((err) => { let error = 'Unknown error'; try { error = JSON.parse(err.responseText)?.message || JSON.parse(err.responseText)?.errors?.message; } catch { // 忽略解析错误 } return { folderError: error }; });这里用
.catch()替代原有的.then(null, errHandler),无需手动创建新Promise,.catch的返回值会自动作为resolved结果传入下一个链。方案2:统一所有异步操作为原生Promise
如果其他克隆方法(cloneA/cloneB等)也返回jQuery Deferred,全部用Promise.resolve()包裹,同时给default分支返回一个已resolved的Promise,避免数组中出现undefined:const clonePromises = cloneServices?.getAds().map((ad: IAds) => { switch(ad.type) { case 'tree': return Promise.resolve(api.cloneA(ad.id)); case 'creativegroup': return Promise.resolve(api.cloneB(ad.id)); case 'creative': return Promise.resolve(api.cloneC(ad.name, ad.id, ad.hasVariations)); case 'trackerresource': return Promise.resolve(cloneTracker(ad)); case 'folder': return Promise.resolve(api.cloneFolder(ad.id)).catch((err) => { let error = 'Unknown error'; try { error = JSON.parse(err.responseText)?.message || JSON.parse(err.responseText)?.errors?.message; } catch { // noop } return { folderError: error }; }); default: // 无法克隆时返回标记值 return Promise.resolve(null); } });方案3:简化错误处理逻辑
无需手动创建Promise,直接在错误回调中返回错误对象即可——无论是jQuery Deferred还是原生Promise,.catch(或.then的错误回调)都会自动将返回值包装为resolved状态:case 'folder': return api.cloneFolder(ad.id).then(null, (err) => { let error = 'Unknown error'; try { error = JSON.parse(err.responseText)?.message || JSON.parse(err.responseText)?.errors?.message; } catch { // noop } return { folderError: error }; });若已将整个链转为原生Promise,该写法同样适用。
验证修改
采用任一方案修改后,Promise.all会正确等待所有异步操作完成,触发后续.then回调并输出show this。
内容的提问来源于stack exchange,提问作者hongkongbboy

