Promises in Depth

Лекция 14

Promises

Promises/A+ - состояние

  • pending / ожидание
  • fullfiled / выполнено
  • rejected / отклонено

Promises/A+ - обертка


function resolver(resolve, reject) {
  return (err, data) => {
    if (err) reject(err);
    else resolve(data);
  }
}

function readDir(dir) {
  return new Promise((res, rej) => {
    fs.readir(dir, resolver(res, rej));
  });
}
    

Promises/A+ - цепочка


function readDir(dir) {
  return new Promise((res, rej) => {
    // читаем файлы из папки
  });
}

function zip(files) {
  return new Promise((res, rej) => {
    // архивируем файлы
  });
}

function sendByEmail(file) {
  return new Promise((res, rej) => {
    // отправляем файл по email
  });
}
    

Promises/A+ - цепочка


readDir('/tmp/images')

  .then((files) => { return zip(files) })

  .then((file) => { return sendByEmail(file) })

  .then(() => { ... })

  .catch((err) => { ... });
    

Promises/A+ - All


readDir('/tmp/images')

  .then((files) => { return zip(files) })

  .then((file) => {
    return Promise.all([
      sendByEmail(user.email, file),
      sendByEmail(admin.email, file)
    ]);
  })

   // results - массив с данными от промиссов
  .then((results) => { ... })

  .catch((err) => { ... });
    

Promises/A+ - Race


readDir('/tmp/images')

  .then((files) => { return zip(files) })

  .then((file) => {
    return Promise.race([
      sendByEmail(user.email[0], file),
      sendByEmail(user.email[1], file)
    ]);
  })

   // result - данные от выполненного промиса
  .then((result) => { ... })

  .catch((err) => { ... });
    

Promises/A+ - особенности


const a = new Promise(...);

a.then((data) => ..., (error) => ...);

!=

a.then(...).catch(...);
    

Promises/A+ - особенности


const a = new Promise(...);

a.then(...);
a.then(...);

!=

a.then(...).then(...);
    

Promises/A+ - особенности


const a = new Promise(...);

a.then(...)
  .catch(...)
  .then(...)
  .catch(...);
    

Async/Await

Async/Await


async function read() {
  let content = await
    fs.readFileAsync('hero.txt', 'utf-8');

  console.log(content);
};

read();
    

Async/Await


async function magic() {
  try {
    const files = await readDir('/tmp/images');

    const archive = await zip(files);

    await Promise.all([
      sendByEmail(user.email, file),
      sendByEmail(admin.email, file)
    ]);

    ...
  } catch (error) {
    ...
  }
}
    

Anti-patterns

Anti-patterns


asyncOp()
  .then(() => new Promise(...));
    

Anti-patterns


async function magic() {
  await asyncOp().then(...);
}
    

Bluebird

Bluebird - Finally


// - 1 -
// after fullfill -> always
Promise.resolve(42)
  .then(() => console.log('after fullfill'))
  .catch(() => console.log('after reject'))
  .finally(() => console.log('always'));
    

Bluebird - Finally


// - 2 -
// after reject -> always
Promise.reject(42)
  .then(() => console.log('after fullfill'))
  .catch(() => console.log('after reject'))
  .finally(() => console.log('always'));
    

Bluebird - Finally


// - 3 -
// after fullfill -> always -> a bit later
Promise.resolve(42)
  .then(() => console.log('after fullfill'))
  .finally(() => console.log('always'))
  .then(() => console.log('a bit later'));
    

Bluebird - Finally


// - 4 -
// after reject -> always -> a bit later
Promise.reject(42)
  .catch(() => console.log('after reject'))
  .finally(() => console.log('always'))
  .then(() => console.log('a bit later'));
    

Bluebird - Finally


// - 5 -
// always -> after reject
Promise.reject(42)
  .then(() => console.log('after fullfill'))
  .finally(() => console.log('always'))
  .then(() => console.log('never'))
  .catch(() => console.log('after reject'));
    

Bluebird - Finally


// - 6 -
// always -> after 1s
Promise.resolve(42)
  .finally(() => {
    console.log('always');

    return delay(1000);
  })
  .then(() => console.log('after 1s'));

function delay(ms) {
  return new Promise(resolve =>
    setTimeout(resolve, ms)
  );
}
    

Bluebird - Catch


class DeannonizationError extends Error {}
class BigBrotherWatchingYouError extends Error {}
    

Bluebird - Catch


// - 1 -
// better run
Promise.reject(new DeannonizationError())
  .catch(
    DeannonizationError,
    () => console.log('better run')
  )
  .catch(
    BigBrotherWatchingYouError,
    () => console.log('too late')
  );
    

Bluebird - Catch


// - 2 -
// too late
Promise.reject(new BigBrotherWatchingYouError())
  .catch(
    DeannonizationError,
    () => console.log('better run')
  )
  .catch(
    BigBrotherWatchingYouError,
    () => console.log('too late')
  );
    

Bluebird - Catch


// - 3 -
// oh no
Promise.reject(new BigBrotherWatchingYouError())
  .catch(
    DeannonizationError,
    BigBrotherWatchingYouError,
    () => console.log('oh no')
  );
    

Bluebird - Catch


// - 4 -
// predicate
Promise.reject({ code: 42 })
  .catch(
    error => error.code === 42,
    () => console.log('error 42')
  );
    

Bluebird - Catch


// - 5 -
// shorthand for checking properties
Promise.reject({ code: 42 })
  .catch(
    { code: 42 },
    () => console.log('error 42')
  );
    

Bluebird - Any


// - 1 -
// 42
Promise.any([
  Promise.reject(40),  // error
  Promise.reject(41),  // error
  Promise.resolve(42), // success
]).then(x => console.log(x));
    

Bluebird - Any


// - 2 -
// 500
Promise.any([
  delay(1000),
  delay(500),
  delay(700),
]).then(x => console.log(x));
    

Bluebird - Any


// - 3 -
// 40 -> 41 -> 42
Promise.any([
  Promise.reject(40),
  Promise.reject(41),
  Promise.reject(42),
]).catch(error =>
  error.forEach(x => console.log(x))
);
    

Bluebird - Any


// - 4 -
// 500 -> 700 -> 1000
Promise.any([
  delayAndReject(1000),
  delayAndReject(500),
  delayAndReject(700),
]).catch(error =>
  error.forEach(x => console.log(x))
);

function delayAndReject(ms) {
  return new Promise((resolve, reject) =>
    setTimeout(() => reject(ms), ms)
  );
}
    

Bluebird - Some


// [40, 41]
Promise.some([
  Promise.resolve(40),
  Promise.resolve(41),
  Promise.reject(42),
], 2).then(x => console.log(x));
    

Bluebird - Map


// - 1.1 -
// [1, 2, 3]
const promises = [1, 2, 3]
  .map(x => Promise.resolve(x));

Promise.all(promises)
  .then(x => console.log(x));
    

Bluebird - Map


// - 1.2 -
// [1, 2, 3]
Promise.map(

  [1, 2, 3],
  x => Promise.resolve(x)

).then(x => console.log(x));
    

Bluebird - Map


// - 2 -
// start of 1000ms timer
// start of 2000ms timer
// end of 1000ms timer
// start of 3000ms timer
// end of 2000ms timer
// end of 3000ms timer
// after 4000ms
Promise.map(

  [1000, 2000, 3000],
  x => delay(x),
  { concurrency: 2 }

).then(x => console.log('after 4000ms'));
    

Bluebird - Map Series


// - 1.1 -
// start of 1000ms timer
// end of 1000ms timer
// start of 2000ms timer
// start of 3000ms timer
// end of 2000ms timer
// end of 3000ms timer
// after 6000ms
Promise.map(

  [1000, 2000, 3000],
  x => delay(x),
  { concurrency: 1 }

).then(x => console.log('after 6000ms'));
    

Bluebird - Map Series


// - 1.2 -
Promise.mapSeries(

  [1000, 2000, 3000],
  x => delay(x)

).then(x => console.log('after 6000ms'));
    

Bluebird - Bind


// {x: 42, y: 43}
Promise.resolve(42)
  .bind({})
  .then(function (x) {
    this.x = x;
    return Promise.resolve(43);
  })
  .then(function (y) {
    this.y = y;
  })
  .then(function () {
    console.log(this)
  });
    

Bluebird - Method


function semiAsyncFn() {
  if (Math.random() > 0.5) {
    return 420;
  }

  return delay(42);
}

const asyncFn = Promise.method(semiAsyncFn);

asyncFn.then(x =>
    console.log(
      'I handle both sync and async results', x
    )
);
    

Bluebird - Tap


// - 1 -
// log 42
// process 42
Promise.resolve(42)
  .tap(x => console.log(`log ${x}`))
  .then(x => console.log(`process ${x}`));
    

Bluebird - Tap


// - 2 -
// start logging
// log 42
// process 42
Promise.resolve(42)
  .tap(x => asyncLogging(x))
  .then(x => console.log(`process ${x}`));

function asyncLogging(x) {
  console.log('start logging');

  return new Promise(resolve => setTimeout(() => {
    console.log(`log ${x}`);
    resolve();
  }, 1000));
}
    

Bluebird - Tap Catch


// - 1 -
// log error 42
// process error 42
Promise.reject(42)
  .tapCatch(x => console.log(`log error ${x}`))
  .catch(x => console.log(`process error ${x}`));
    

Bluebird - Tap Catch


// - 2 -
Promise.reject(new DeannonizationError())
  .tapCatch(
      DeannonizationError,
      x => console.log('log deannonimization')
  )
  .tapCatch(
    BigBrotherWatchingYouError,
    x => console.log('log bbwy')
  )
  .catch(
    DeannonizationError,
    () => console.log('process deannonimization')
  )
  .catch(
    BigBrotherWatchingYouError,
    () => console.log('process bbwy')
  );
    

Bluebird - Cancellation


Promise.config({ cancellation: true });
    

Bluebird - Cancellation


// - 1 -
const promise = delay(1000)
  .then(() => console.log('We will never see this'));

promise.cancel();
    

Bluebird - Cancellation


// - 2 -
const promise = delay(1000)
  .then(() => console.log('We will never see this'));

promise.cancel();

function delay(ms) {
  return new Promise((resolve, reject, onCancel) => {
    const timer = setTimeout(() =>  {
       console.log('and this one too');
       resolve();
    }, ms);

    onCancel(() => clearTimeout(timer));
  });
}
    

Bluebird - Cancellation


function delay(ms) {
  return new Promise((resolve, reject, onCancel) => {
    const timer = setTimeout(() =>  {
       console.log('timer fired');
       resolve();
    }, ms);

    onCancel(() => {
      console.log('timer cancelled');
      clearTimeout(timer);
    });
  });
}
    

Bluebird - Cancellation


// - 3 -
// timer fired, A, B
const source = delay(1000);

const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
    

Bluebird - Cancellation


// - 4 -
// timer cancelled
const source = delay(1000);

const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));

source.cancel();
    

Bluebird - Cancellation


// - 5 -
// timer fired, B
const source = delay(1000);

const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));

consumerA.cancel();
    

Bluebird - Cancellation


// - 6 -
// timer cancelled
const source = delay(1000);

const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));

consumerA.cancel();
consumerB.cancel();
    

Bluebird - Cancellation


// - 7 -
// timer fired, A
const source = delay(1000);

const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
const consumerС = consumerB.then(() => console.log(`С`));

consumerС.cancel();
    

Bluebird - Timeout


// Time's up!
delay(1000)
  .timeout(100)
  .then(() => console.log(`We will never see this`))
  .catch(
    Promise.TimeoutError,
    error => console.log(`Time's up!`)
  )
    

Bluebird - Delay


Promise.delay(1000)
  .then(() => console.log(`after 1s`));