JSlang.dev

JavaScript Deep Dives with Wolfram. Since 2015
Inclusive, Test-Driven, Spec-Focused, Collaborative.

February 18, 2021
TestsSource
JavaScript Promises

Source Code

The source code we wrote during the event.

// Source at: https://codeberg.org/wolframkriesing/jslang-meetups
import {strict as assert} from 'assert';

describe('Promises - How to collect more than one promise (in a loop?)', () => {
  it('the value of one resolved promises can be retreived', () => {
    return Promise.resolve(5)
      .then((value) => {
        assert.equal(value, 5);
      });
  });
  it('the value of one resolved promises can be retreived (testing mocha)', (done) => {
    Promise.resolve(5)
      .then((value) => {
        assert.equal(value, 5);
        done();
      });
  });
  it('collect two promises using Promise.all()', () => {
    return Promise.all([
      Promise.resolve(5),
      Promise.resolve(10),
    ]).then((values) => {
      assert.deepEqual(values, [5, 10]);
    })
  });
  it('Promise.all() keeps the order independent of the timing', () => {
    return Promise.all([
      new Promise((resolve) => { setTimeout(() => resolve(5), 10) }),
      Promise.resolve(10),
    ]).then((values) => {
      assert.deepEqual(values, [5, 10]);
    })
  });
});

describe('What is Promise.any?', () => {
  it('one resolving promise returns the value', () => {
    const onePromise = [Promise.resolve(5)];
    return Promise.any(onePromise).then((value) => {
      assert.equal(value, 5);
    });
  });
  it('no promise in Promise.any() rejects', () => {
    const noPromises = [];
    return Promise.any(noPromises)
      .catch(error => {
        assert(error instanceof AggregateError);
      });
  });
  it('one rejected promise rejects with an AggregateError and an array `errors` with one entry', () => {
    return Promise.any([
      Promise.reject('hi 5')
    ]).catch((error) => {
      assert(error instanceof AggregateError);
      assert.deepEqual(error.errors, ['hi 5']);
    })
  });
  it('two rejected and one later resolved promise, will return the first resolved one', () => {
    return Promise.any([
      Promise.reject('1'),
      Promise.reject('2'),
      new Promise((resolve) => { setTimeout(() => resolve('ok'), 100); }),
    ]).then((value) => {
      assert.equal(value, 'ok');
    })
  });
  it('does NOT abort the execution of other promises once the first one resolves (see the 100ms test run time)', () => {
    const promise1 = new Promise((resolve) => { setTimeout(() => resolve('I am 1'), 15) });
    const promise2 = new Promise((resolve) => { setTimeout(() => resolve('I am 2'), 100) });
    return Promise.any([
      promise1,
      promise2
    ]).then((result) => {
      assert.equal(result, 'I am 1');
      return promise2;
    });
  });
  it('two rejected and two later resolved promises, will return the first one', () => {
    return Promise.any([
      Promise.reject('1'),
      new Promise((resolve) => { setTimeout(() => resolve('ok 1'), 100); }),
      Promise.reject(7),
      new Promise((resolve) => { setTimeout(() => resolve('ok 2'), 50); }),
    ]).then((value) => {
      assert.equal(value, 'ok 2');
    })
  });

  xit('two racing promises, will return the first one', () => {
    return Promise.any([
      new Promise((resolve) => { setTimeout(() => resolve('ok 1'), 51); }),
      new Promise((resolve) => { setTimeout(() => resolve('ok 2'), 50); }),
    ]).then((value) => {
      assert.equal(value, 'ok 2');
    })
  });
  xit('FLAKY: two racy promise creations', () => {
    const a = new Promise((resolve) => { setTimeout(() => resolve('ok 1'), 51); });

    // just a loop to "make it slow"
    let count = 1;
    for (let i=0; i<1000; i++) {
      count = i * Math.random();
    }

    const b = new Promise((resolve) => { setTimeout(() => resolve('ok 2'), 50); });
    return Promise.any([a, b]).then((value) => {
      assert.equal(value, 'ok 2');
    })
  });

  it('does NOT work with a number as parameter', () => {
    return Promise.any(1)
      .catch((e) => {
        assert(e instanceof TypeError);
      })
  });
  it('an array of numbers returns the first number', () => {
    return Promise.any([1,2,3])
      .then(firstNumber => {
        assert.equal(firstNumber, 1);
      })
  });
});

describe('How does Promise.prototype.finally work?', () => {
  it('will be called after a promise resolved', () => {
    return new Promise((resolve) => {
      Promise
        .resolve(1)
        .finally(() => {
          resolve();
        });
    });
  });
  it('will be called after a rejected promise was handled', () => {
    const rejectedPromise = Promise.reject(Error(''));
    return new Promise((resolve) => {
      rejectedPromise.catch(() => {}).finally(() => {
        resolve();
      });
    });
  });
  it('will be called is rejected', () => {
    const rejectedPromise = Promise.reject(Error(''));
    return new Promise((resolve) => {
      rejectedPromise.finally(() => {
        resolve();
      }).catch(() => {});
    });
  });
  it('inside finally() we throw then catch() after it in the promise chain catches it', () => {
    const resolvedPromise = Promise.resolve();
    return new Promise((resolve) => {
      resolvedPromise.finally(() => {
        throw Error('');
      }).catch(() => {
        resolve();
      });
    });
  });
  it('finally can be chained', () => {
    const resolvedPromise = Promise.resolve();
    return new Promise((resolve) => {
      resolvedPromise.finally(() => {
        throw Error('');
      }).catch(() => {
      }).finally(() => {
        throw Error('');
      }).catch(() => {
        resolve();
      });
    });
  });
  it('finally can be chained on finally', () => {
    const resolvedPromise = Promise.resolve();
    return new Promise((resolve) => {
      resolvedPromise
        .finally(() => {})
        .finally(() => {})
        .finally(() => {})
        .finally(() => {})
        .finally(resolve)
      ;
    });
  });
  it('an error bubbles up the chain in finally', () => {
    const resolvedPromise = Promise.resolve();
    return new Promise((resolve) => {
      resolvedPromise
        .finally(() => {
          throw Error('42');
        }).catch((e) => {
          assert.equal(e.message, '42');
          resolve();
        });
    });
  });
  it('a rejected promise does fall through a finally', async () => {
    const rejectedPromise = Promise.reject();
    const result = await rejectedPromise
      .finally(() => { return 'in finally'; })
      .catch(() => { return 'in catch'; });

    assert.equal(result, 'in catch');
  });
  it('finally after a rejected promise is executed and shadows the actual rejection', async () => {
    const rejectedPromise = Promise.reject('rejected promise')
      .finally(() => { throw Error('finally was run'); })
      .catch((error) => { return error.message; })
    ;
    const result = await rejectedPromise;
    assert.equal(result, 'finally was run');
  });
  it('value from finally() does NOT override value from rejected promise', async () => {
    const rejectedPromise = Promise.reject('rejected')
      .finally(() => { return 'in finally'; })
      .catch((error) => { return error; });

    assert.equal(await rejectedPromise, 'rejected');
  });
  it('result of a rejected promise is NOT an error', () => {
    return Promise.reject('a string')
      .catch((error) => {
        assert.equal(error, 'a string');
      });
  });
  it('awaiting a Promise.reject throws its parameter', async () => {
    try {
      await Promise.reject('my param');
    } catch (e) {
      assert.equal(e, 'my param');
    }
  });
  it('a rejected value in finally is NOT retreivable by a then() after it', async () => {
    const breadcrumbs = [];
    const rejectedPromise = Promise.reject('rejected')
      .finally((value) => {
        breadcrumbs.push('in finally()');
        breadcrumbs.push(value);
        return 'from finally';
      }).then((value) => {
        breadcrumbs.push('in then()');
        breadcrumbs.push(value);
      }).catch((error) => {
        breadcrumbs.push('in catch()');
        breadcrumbs.push(error);
      })
    ;
    await rejectedPromise;
    assert.deepEqual(breadcrumbs, ['in finally()', undefined, 'in catch()', 'rejected']);
  });
  it('a resolved value in finally is NOT retreivable by a then() after it', async () => {
    const breadcrumbs = [];
    const rejectedPromise = Promise.resolve('resolved')
      .finally((value) => {
        breadcrumbs.push('in finally()');
        breadcrumbs.push(value);
        return 'from finally';
      }).then((value) => {
        breadcrumbs.push('in then()');
        breadcrumbs.push(value);
      }).catch((error) => {
        breadcrumbs.push('in catch()');
        breadcrumbs.push(error);
      })
    ;
    await rejectedPromise;
    assert.deepEqual(breadcrumbs, ['in finally()', undefined, 'in then()', 'resolved']);
  });

  xit('A delayed Promise.reject WILL fail the test', () => {
    setTimeout(() => Promise.reject(Error('')), 1);
  });
});