There are a number of pernicious issues when migrating to jQuery 3 with respect to how the Promises A+ spec changes. 
-
All resolutions happen on the next callstack (no more sync). This is a major issue in tests that regularly assume
$.Deferred.resolve()will be resolved synchronously. The solution in mocha is to either return the promise to thebeforeEachhandler to chain. If multiple promises are resolved in the underlying code.then()chains need to be added to the return chain for each promise handler. Another solution is to use a https://github.com/YuzuJS/setImmediate wrapper for the returned Promise. -
Errors in Promises are now handled by
throw. You can return a rejected deferred ($.Deferred.reject(...)) but it's preferable to use the nativethrow. -
If you add an error handler (either via
then(..., err => ...)orcatch(err => ...)) to a promise chain and you do NOT throw again, the promise chain is considered resolved. As such,throwanother error if you want the chain to stay rejected.// the below propagates the error in jQuery 2 and resolves the promise chain in jQuery 3. $.Deferred().reject(123) .then(() => {}, err => 'I am the new error message') .then(newErr => console.log('I am called in jQuery 3'), newErr => console.error('I am called in jQuery 2')) // for the desired behavior, throw the error in jQuery 3 $.Deferred().reject(123) .then(() => {}, err => { throw 'I am the new error message' }) .then(console.log, console.error)
-
Cannot resolve or reject with multiple arguments with Promises (
Promise.resolve/rejectvsDeferred.resolve/reject).// jQuery Deferred allows for non-compliant multi-argument resolution/rejection $.Deferred().reject(1,2,3).catch((a,b,c) => { console.error("jQuery 3 Deferred rejects with ", a, b, c); // 1 2 3 }) // Native promises ignore extra arguments Promise.reject(1,2,3).catch((a,b,c) => { console.error("Native Promise can only reject with on value: ", a, b, c); // 1 })
-
use
Promise.allnot$.when(requires arrays)// so $.when(whenA, whenB).then((a, b) => {...}) // becomes Promise.all([whenA, whenB]).then(([a, b]) => {...})
-
no more
done/failwhen you use native Promises (alwayscan be handled withfinally). -
no more
promise.statewhen you use native Promises.
-
Use Deferred utility class (below) if you want native Promises with
.resolve().reject()functionality in tests. Note you'll lose the ability to resolve/reject with multiple values (which we should avoid anyhow), plus you cannot usedonefailoralways. -
Mocha will complain if you return a rejected promise in a
beforeEachoritblock. However, if you catch the error in the promise chain, you may prevent your code from receiving the error via a stub. Therefore, if you want to use a rejected promise as the return value of a MochabeforeEachoritand in the return value of a stub, catch the error in the return Mocha statement after you’ve already given the rejected promise to your stubbeforeEach(() => { const promise = Promise.reject(); sinon.stub(..., promise); // don't do `sinon.stub(..., Promise.reject().catch(() => {})) otherwise that will resolve // ensure mocha waits for next tick and prevent node from complaining about rejected promises return promise.catch(() => {}); })
-
One way to test in mocha is to use a async test callback, remembering that if called with any argument, that becomes the error
it('resolves correctly', done => { promise.then(() => done(), done) }) it('rejects correctly', done => { promise.then(() => done('Should not have resolved!'), () => done()) })