Asynchronous JavaScript

Що значить "(а)синхронно"?

В програмуванні - два види "одночасності"

паралелізм

vs.

серіалізм (aka співвимірність, aka concurrency)

Q: Чи потрібний нам справжній паралелізм?
A: Майже завжди ні.

якщо операції не пов'язані між собою - who cares? 🤷‍♀️
якщо пов'язані - можемо явно задавати їх порядок (детермінізм)

Виконання-до-завершення (run-to-completion)

//Excerpt From: Kyle Simpson. “You Don't Know JS: Async & Performance.”

var a = 1;
var b = 2;

function foo() {
    a++;
    b = b * a;
    a = b + 3;
}

function bar() {
    b--;
    a = 8 + b;
    b = a * 2;
}

// ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );”

race condition, але — тільки два можливих результати

Які механізми існували в ядрі JS для роботи з асинхронними запитами до 2015 року?

Які механізми існували в ядрі JS для роботи з асинхронними запитами до 2015 року?

Частина перша.

ні обіцянок, ні пробачень


(pre-Promise JavaScript)

Якщо асинхронності немає, доведеться її винайти


Web API, events+callbacks

// ajax - some generic library for asynchronous calls 
// based on events & callbacks

listen('click', function handler(evt) {
  setTimeout(function request() {
    ajax('http://some.url.1', function response(res) {
      if (res.status === 200) {
        handleSuccess(res)
      } else {
        handleFailure(res)
      }
    })
  }, 500)
})
// ajax - some generic library for asynchronous calls 
// based on events & callbacks

listen('click', handler)

function handler(evt) {
  setTimeout(request, 500)
})

function request() {
    ajax('http://some.url.1', response)
  }

function response(res) {
  if (res.status === 200) {
    handleSuccess(res)
  } else {
    handleFailure(res)
  }
}
doA(function() {
  doB()

  doC(function() {
    doD()
  })

  doE()
})

doF()
doA(function() { // 1
  doB() // 3

  doC(function() { // 4
    doD() // 6
  })

  doE() // 5
})

doF() // 2
analytics.trackPurchase(purchaseData, function() {
  chargeCreditCard()
  displayThankyouPage()
})

Trust issues

var tracked = false

analytics.trackPurchase(purchaseData, function() {
  if (!tracked) {
    tracked = true
    chargeCreditCard()
    displayThankyouPage()
  }
})

Візуальна "pyramid of doom" — не єдина проблема колбеків:

  • hardcoded handling для кожного окремого випадку (ad hoc)
  • неочевидний порядок виконання (синхронність/асинхронність функцій впливає)
  • вимушений Inversion of Control (ризик, що колбек не спрацює, або спрацює забагато разів)

Частина друга.

меньше всего нужны мне твои колбэки


(post-Promise JavaScript)

Promise API (ES2015+)

const result = new Promise((resolve, reject) => {
  if (checkPasses) {
    resolve('resolve value')
  } else {
    reject('rejection reason')
  }
})
  .then(
    function fulfilled(value) {
      return value
    },
    function rejected(reason) {
      throw new Error(reason)
    }
  )
  .catch(err => console.error(err))

Як визначити проміс?

p instanceof Promise // ❌ insufficient

Thenable type check
(duck typing 🦆)

function isThenable(value) {
  return (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  )
}
const pseudoPromise = {
  then: function(cb, errcb) {
    cb('Success!')
    errcb('Error!')
  }
}

pseudoPromise
  .then(
    function fulfilled(val) {
      console.log(val) // "Success!"
    },
    function rejected(err) {
      // WTF
      console.log(err) // "Error!"
    }
  )
const pseudoPromise = {
  then: function(cb, errcb) {
    cb('Success!')
    errcb('Error!')
  }
}

Promise.resolve(pseudoPromise)
  .then(
    function fulfilled(val) {
      console.log(val) // "Success!"
    },
    function rejected(err) {
      // doesn't run
      console.log(err) // "Error!"
    }
  )

Як проміс вирішує проблеми колбеків?

  • Імутабельність - виконаний проміс лишається незмінним (Un-inversion of Control, колбек працює не більше одного разу)
  • за рахунок .then порядок виконання зверху вниз
  • Розширений інструментарій абстрактних методів для контролю flow (зменшує необхідність писати ad hoc код): Promise.resolve, Promise.reject, Promise.all, Promise.race, Promise.allSettled, Promise.any

Практика!

Async functions

const promise = async function() {
  try {
    const res = await fetch('some-url')
    const data = await res.json()
    return data
  } catch(err) {
    throw err
  }
}
function httpGet(url, responseType = '') {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest()
    request.onload = function() {
      if (this.status === 200) {
        // Success
        resolve(this.response)
      } else {
        // Something went wrong (404 etc.)
        reject(new Error(this.statusText))
      }
    }
    request.onerror = function() {
      reject(new Error('XMLHttpRequest Error: ' + this.statusText))
    }
    request.open('GET', url)
    xhr.responseType = responseType
    request.send()
  })
}

Більше:

async-js

By Yevhen Orlov