Asynchronous in JS

Why does it matter?

JS is monothreaded, everywhere

The olympics of async!

The event loop - Question 1

function f() {
  console.log("foo");
  setTimeout(g, 0);
  console.log("baz");
  h();
}

function g() {
  console.log("bar");
}

function h() {
  console.log("blix");
}

f();

A)   foo, bar, baz, blix

B)   foo, baz, blix, baz

C)   foo, baz, bar, blix

D)    Answer D

The event loop - Answer 1

function f() {
  console.log("foo");
  setTimeout(g, 0);
  console.log("baz");
  h();
}

function g() {
  console.log("bar");
}

function h() {
  console.log("blix");
}

f();
  1. foo
  2. baz
  3. blix
  4. bar

foo, bar, baz, blix

foo, baz, blix, baz

foo, baz, bar, blix

Answer D

The event loop - Question 2

function f() {
  let i = null;
  for (i = 0; i < 3; i++) {
    setTimeout(() => {
      console.log(i)
    }, 0);
    console.log(i);
  }
}

f();

A)   0, 0, 1, 1, 2, 2

B)    0, 1, 2, 3, 3, 3

C)    0, 1, 2, 0, 1, 2

D) Other / It depends

The event loop - Answer 2

function f() {
  let i = null;
  for (i = 0; i < 3; i++) {
    setTimeout(() => {
      console.log(i)
    }, 0);
    console.log(i);
  }
}

f();

0, 0, 1, 1, 2, 2

0, 1, 2, 3, 3, 3

0, 1, 2, 0, 1, 2

Other / It depends

The event loop

Async functions

Callbacks

  • Easy to write and understand...
  • ... when you only have a couple
  • Callback hell!
  • Hard to test

Callbacks - example

toggleCall = () => {
    this.upsideDown.enter(() => {
      this.setState({ strangerStatus: 'In upside down' });

      this.upsideDown.findDemogorgon(location => {
        this.setState({ strangerStatus: `Demogorgon in ${location}` });

        this.upsideDown.killDemogorgon(result => {
          if (result === 'SUCCESS') {
            this.setState({ strangerStatus: 'Demogorgon dead ️☠️' });
            return;
          }

          this.setState({ strangerStatus: 'You are dead ☠️' });
        });
      });
    });
};

Promise

  • Easier to chain
  • Easier to manage errors
  • Can be used natively in browser & node
  • Not really easy to test
  • Confusing at first

Promise - Question 3

Promise.resolve('foo')
  .then(Promise.resolve('bar'))
  .then(result => {
    console.log(result);
  });

A) foo

B) bar

C) null

D) throws an error

Promise - Answer 3

Promise.resolve('foo')
  .then(Promise.resolve('bar'))
  .then(result => {
    console.log(result);
  });
Promise.resolve('foo')
  .then(result => {
    return Promise.resolve('bar')
  })
  .then(result => {
    console.log(result);
  });

foo

bar

null

It throws an error

Promise - Example

toggleCall = () => {
    return this.upsideDownPromise
      .enter()
      .then(() => {
        this.setState({ strangerStatus: 'In upside down' });
        return this.upsideDownPromise.findDemogorgon();
      })
      .then(location => {
        this.setState({ strangerStatus: `Demogorgon in ${location}` });
        return this.upsideDownPromise.killDemogorgon();
      })
      .then(result => {
        if (result === 'SUCCESS') {
          this.setState({ strangerStatus: 'Demogorgon dead ️☠️' });
          return;
        }

        this.setState({ strangerStatus: 'You are dead ☠️' });
      });
};

Async/await - Example

toggleCall = async () => {
    await this.upsideDownPromise.enter();
    this.setState({ strangerStatus: 'In upside down' });

    const location = await this.upsideDownPromise.findDemogorgon();
    this.setState({ strangerStatus: `Demogorgon in ${location}` });

    const result = await this.upsideDownPromise.killDemogorgon();

    if (result === 'SUCCESS') {
      this.setState({ strangerStatus: 'Demogorgon dead ️☠️' });
      return;
    }

    this.setState({ strangerStatus: 'You are dead ☠️' });
};

Saga - Example

export function* toggleCall(): Generator<*, *, *> {
  yield call([upsideDown, upsideDown.enter]);
  yield put(updateStatus('In under world'));

  const place = yield call([upsideDown, upsideDown.findDemogorgon]);
  yield put(updateStatus(`Found demogorgon in ${place}`));

  const result = yield call([upsideDown, upsideDown.killDemogorgon]);
  if (result === 'SUCCESS') {
    yield put(updateStatus(`Demogorgon is dead!`));
  } else {
    yield put(updateStatus(`You are dead`));
  }
}

MobX Observable

export default class UpsideDownStore {
  upsideDown = new UpsideDownPromise();
  status = observable('In real world');

  toggleCall = action(async () => {
    await this.upsideDown.enter();

    runInAction(() => {
      this.status.set('In under world');
    });

    const place = await this.upsideDown.findDemogorgon();
    runInAction(() => {
      this.status.set(`Found demogorgon in ${place}`);
    });

    const result = await this.upsideDown.killDemogorgon();
    runInAction(() => {
      if (result === 'SUCCESS') {
        this.status.set('Demogorgon is dead!');
      } else {
        this.status.set('You are dead');
      }
    });
  });
}

VueX

import * as types from './mutation-types'

export const toggleAction = async ({ commit, state }) => {
  await state.upsideDownPromise.enter()
  commit(types.UPDATE_STATUS, 'In upside down')

  const location = await state.upsideDownPromise.findDemogorgon()
  commit(types.UPDATE_STATUS, `Demogorgon in ${location}`)

  const result = await state.upsideDownPromise.killDemogorgon()
  if (result === 'SUCCESS') {
    commit(types.UPDATE_STATUS, 'Demogorgon dead ️☠️')
    return
  }

  commit(types.UPDATE_STATUS, 'You are dead ☠️')
}
<template>
    <ButtonWithStatus
        :toggle-call="toggleAction"
        :stranger-status="storedStatus"
        button-text="VueX call"
    ></ButtonWithStatus>
</template>

<script>
import { mapActions, mapState } from 'vuex'
import ButtonWithStatus from './ButtonWithStatus'

export default {
  name: 'VuexCall',
  components: {
    ButtonWithStatus
  },
  methods: {
    ...mapActions(['toggleAction'])
  },
  computed: mapState({
    storedStatus: state => state.status
  })
}
</script>

One last thing

Mulithread?

How to use all your processor cores?

  • Cluster mode PM2
  • NapaJS

Links

Async in JS

By Kevin Raynel

Async in JS

  • 970