describe('Promise', () => { it('Promise executor is run SYNCHRONOUSLY', () => { let executorRun = false; new Promise(function executor() { executorRun = true; }); expect(executorRun).toBe(true); }); it('you can resolve a promise', (done) => { new Promise((resolve) => setTimeout(resolve, 1)) .then(done); }); it('... or you can reject a promise', (done) => { new Promise((resolve, reject) => setTimeout(reject, 1)) .then(undefined, done); }); it('An error inside the executor, rejects the promise', (done) => { new Promise(function executor() { throw 'Error'; }).catch(done); }); it('you can use Promise.resolve() to wrap values or promises', (done) => { function iMayReturnAPromise() { return Math.random() >= 0.5 ? Promise.resolve() : 5; } Promise.resolve(iMayReturnAPromise()).then(done); }); it('you can use Promise.resolve() to execute something just after', (done) => { let arr = []; Promise.resolve().then(() => arr.push(2)); arr.push(1); setTimeout(() => { expect(arr).toEqual([1, 2]); done(); }, 1); }); /** @see https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ **/ it('Promise.resolve() is normally executed before setTimeout(..., 0)', (done) => { let arr = []; setTimeout(() => arr.push('timeOut'), 0); Promise.resolve().then(() => { arr.push('resolve'); }); setTimeout(() => { expect(arr).toEqual(['resolve', 'timeOut']); done(); }, 1); }); it('you can create rejected promises', (done) => { Promise.reject('reason').catch(done); }); /* it('pay attention to "Uncaught (in promise) ..."', () => { Promise.reject('The error').catch(); // Outputs in the console Uncaught (in promise) The error }); */ // Chaining promises it('you can chain promise because .then(...) returns a promise', (done) => { fetch('https://jsonplaceholder.typicode.com/posts/1') .then(response => response.json()) .then(json => expect(json.userId).toBe(1)) .then(done); }); it('you can use the fail callback of .then(success, fail) to handle rejected promises', (done) => { Promise.reject() .then(function success() { throw 'I must not be executed'; }, function fail() { done(); }); }); it('... or you can use .catch() to handle rejected promises', (done) => { Promise.reject() .then(function success() { throw 'I must not be executed'; }) .catch(done); }); it('also .catch() returns a promise, allowing promise chaining', (done) => { Promise.reject() .catch(() => undefined) .then(done); }); it('you must return a rejected promise if you want to execute the next fail callback', (done) => { function someApiCall() { return Promise.reject('Error'); } someApiCall() .catch((err) => { console.error(err); return Promise.reject(err); // Without this line, .catch gets not called }) .catch(done); }); it('... or you can throw an error if you want to execute the next fail callback', (done) => { function someApiCall() { return Promise.reject('Error'); } someApiCall() .catch((err) => { console.error(err); throw err; // Without this line, .catch gets not called }) .catch(done); }); it('values returned inside .then()/.catch() callbacks are provided to the next callback', (done) => { Promise.resolve(1) .then(value => value + 1) .then(value => expect(value).toBe(2)); Promise.reject(1) .catch(value => value + 1) .then(value => expect(value).toBe(2)); setTimeout(() => { done(); }, 1); }); // New await/async syntax it('you can use the new await/async syntax', async () => { function timeout(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } const start = Date.now(); const delay = 200; await timeout(delay + 1); // Just 1ms tolerance expect(Date.now() - start).toBeGreaterThanOrEqual(delay); }); it('an async function returns a promise', (done) => { async function iAmAsync() { return 1; } iAmAsync() .then((val) => expect(val).toBe(1)) .then(done); }); it('await just awaits a promise resolution', async (done) => { await Promise.resolve(); done(); }); it('await will throw an error if the promise fail', async (done) => { try { await Promise.reject(); fail('I will not be executed'); } catch (err) { done(); } }); it("Don't use new Promise(...), prefer chaining", (done) => { const url = 'https://jsonplaceholder.typicode.com/posts/1'; function badlyDesignedCustomFetch() { return new Promise((resolve, reject) => { fetch(url).then((response) => { if (response.ok) { resolve(response); } else { reject('Fetch failed'); } }); }); } function wellDesignedCustomFetch() { return fetch(url).then((response) => { if (!response.ok) { return Promise.reject('Fetch failed'); } return (response); }); } Promise.all([badlyDesignedCustomFetch(), wellDesignedCustomFetch()]).then(done); }); // Parallel execution of promises it('you can use Promise.all([...]) to execute promises in parallel', (done) => { const url = 'https://jsonplaceholder.typicode.com/posts'; const p1 = fetch(`${url}/1`); const p2 = fetch(`${url}/2`); Promise.all([p1, p2]) .then(([res1, res2]) => { return Promise.all([res1.json(), res2.json()]) }) .then(([post1, post2]) => { expect(post1.id).toBe(1); expect(post2.id).toBe(2); }) .then(done); }); it('Promise.all([...]) will fail if any of the promises fails', (done) => { const p1 = Promise.resolve(1); const p2 = Promise.reject('Error'); Promise.all([p1, p2]) .then(() => { fail('I will not be executed') }) .catch(done); }); it("if you don't want Promise.all() to fail, wrap the promises in a promise that will not fail", (done) => { function iMayFail(val) { return Math.random() >= 0.5 ? Promise.resolve(val) : Promise.reject(val); } function promiseOr(p, value) { return p.then(res => res, () => value); } const p1 = iMayFail(10); const p2 = iMayFail(9); Promise.all([promiseOr(p1, null), promiseOr(p2, null)]) .then(([val1, val2]) => { expect(val1 === 10 || val1 === null).toBe(true); expect(val2 === 9 || val2 === null).toBe(true); }) .catch(() => { fail('I will not be executed') }) .then(done); }); it('Promise.race([...]) will resolve as soon as one of the promises resolves o rejects', (done) => { const timeout = new Promise((resolve, reject) => setTimeout(reject, 100)); const data = fetch('https://jsonplaceholder.typicode.com/posts/1'); Promise.race([data, timeout]) .then(() => console.log('Fetch OK')) .catch(() => console.log('Fetch timeout')) .then(done); }); });