JS. Управление выполнением. Планирование

Agenda

  • try/catch
  • Планирование
  • Promise
  • async/await

try/catch/finally

try {
  let a = 3;
} catch {
  console.log('Ошибка!')
}

console.log('Готово!')
try {
  whatsWrong;
} catch {
  console.log('Ошибка!')
}

console.log('Готово!')

OK + готово

Ошибка + готово

try/catch/finally

Примечание. Early error

try {
  {{{{{{{{{{{{
} catch(e) {
  alert("?????");
}

Ошибки

function division(a,b){
  if(b === 0) {
    throw "Division by zero!"
  }
  return a / b ;
}

try {
 division(10/5) // ??
 division(2/0) // ??
} catch(e){
  console.error('???') // ???
  console.error(e) // ???
}

throw

throw - "(про)бросить". создает ошибку переданного операнда. операнд может быть чем угодно.

Для более информативных сообщений используются объект ошибки Error и его вариации.

Объект ошибки пораждается функцией (конструктором) Error

Объект ошибки

function division(a,b){
  if(b === 0) {
    throw new Error("Division by zero!")
  }
  return a / b ;
}

try {
 division(2/0) // ??
} catch(e){
  console.error(e) 
  // { name: "Error", message: "Division by zero!", stack: '...'}
}

Объект ошибки

  • name - имя ошибки
  • message - наше сообщение с которым была вызвана ошибка
  • stack - строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки.

Разновидности ошибок

  • Error - "универсальная" ошибка
  • RangeError - за пределами диапазона. Массив.
  • TypeError - ошибка типа
  • AggregateError

  • EvalError
  • SyntaxError
  • ReferenceError

Ошибки - часть глобального объекта

Свои ошибки

class MathError extends Error {
  constructor(expression, message){
    super(`${expression}. ${message}`)
  }
}

function divide(a,b){
  if(b===0){
    throw new MathError('a=${a}/b=${b}', 'Second parameter is zero')
  }
  return a/b
}

try {
  divide(1,0)
} catch(e){
  console.error(e.message) //'a=1/b=0. Second parameter is zero'
}

преброс ошибок.

import myMath from 'external-math-library'

try {
  // user input
  myMath.divide(1,0)
  myMath.divide(1, -1)
} catch(e){
  if(e instanceof myMath.DivideByZeroError){
    throw e
  }
  console.error('another error')
}

Best practicies

  • Писать информативные сообщения ошибки
  • Если ошибка требует определенной логики - выносить в класс
  • Класс наследуется от ошибки (Error*)
  • Лучше избегать переброса ошибки
  • Не изменять свойство stack

* если не понятно что будет за ошибка

Задачка*

function getResult(){
  try {
    return 5
  } finally {
    console.log('Called?')
    return 10
  }
}

const r = getResult() // ???

Планирование

в JS можно запланировать выполнение некоторого кода.

Планирование работает через передачу функции (управляющая фукнция), которая выполнится по "планированию"

Планирование

Планирование. Функции

  • setTimeout
  • setInterval
  • Promise
  • schedule API*

* не будем проходить

setTimeout/setInterval

const delay = 100;
const timerId = setInterval(function time(){
  console.log('Im delayed!')
}, delay)

console.log(timerId) // int. например 102
clearInterval(timerId) // очистка интервала

Особенности

setTimeout(func) или setTimeout(func, 0).

setTimeout(func, 0, ...params)

А если надо фиксированный delay?

Вложенный setTimeout

const delay = 100;
setTimeout(function run(){ // 1
  console.log('Call!')
  setTimeout(run, delay); // 2
},delay)

Подводные камни

const myArray = ["zero", "one", "two"];
myArray.myMethod = function (sProperty) {
  if(arguments.length > 0){
    console.log(this[sProperty])
  } else {
    console.log(this)
  }
};

myArray.myMethod(); // "zero,one,two"
myArray.myMethod(1); // "one"

setTimeout(myArray.myMethod, 1.0 * 1000); // "[object Window]"
setTimeout(myArray.myMethod, 1.5 * 1000, "1"); // "undefined"

Подводные камни

const myArray = ["zero", "one", "two"];
myArray.myMethod = function (sProperty) { /** реализация */};

// решение 1?
setTimeout.call(myArray, myArray.myMethod, 2.0 * 1000); // ошибка
setTimeout.call(myArray, myArray.myMethod, 2.5 * 1000, 2); // ошибка

// решение 2
setTimeout(function () {
  myArray.myMethod();
}, 2.0 * 1000);
setTimeout(function () {
  myArray.myMethod("1");
}, 2.5 * 1000);

Подводные камни

const myArray = ["zero", "one", "two"];
const myBoundMethod = function (sProperty) {
  if(arguments.length > 0){
    console.log(this[sProperty])
  } else {
    console.log(this)
  }
}.bind(myArray);

myBoundMethod(); // prints "zero,one,two" because 'this' is bound
myBoundMethod(1); // prints "one"
setTimeout(myBoundMethod, 1.0 * 1000); // "zero,one,two"
setTimeout(myBoundMethod, 1.5 * 1000, "1"); // "one"

Лучшие практики

  1. если нужен точный таймер между окончанием функциями - используй вложенный setTimeout
  2. не забываем очищать таймеры
  3. использовать стрелочные функции(кроме п.1)
  4. не передавать функцию без вызова
  5. не использовать замыкания(this)

Callback

function fetchResource(url, callback){ /** реализация */}

fetchResource('res-1', (first) => {
  fetchResource('res-2', (second) => {
    fetchResource('res-3', (third) => {
      // сделать что-то с first, second, third
    })
  })
})

Promise

Объект(класс) который используется в асинхронных операциях

Асинхронная операция - это некий набор инструкции, которые выполняются(или вызываются) не последовательно относительно написанного кода

Пример

const myPromise = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("done"), 1000);
});

myPromise
  .then((result) => {
    console.log(result);
  })
  .catch((err) => console.error(err))
  .finally(() => {
  	console.log('im finally executed')
  });

console.log('hi!')
// 1. hi
// 2. done
// 3. im finally executed

Объяснение

Потребители

  • then - для обработки успешного результата
  • catch - для обработки ошибки
  • finally - очистка

Порядок имеет значение!

const promise = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("done"), 1000);
});

promise
  .finally(() => {
  	console.log('im finally executed')
  })
  .then((result) => {
    console.log(result);
  })
  .catch((err) => console.error(err))

console.log('hi!')

// 1. hi
// 2. Im finally executed
// 3. done

Лучшие практики

  • использовать функцию catch для ошибок
  • вызывать функцию reject с объектом ошибки(new Error)
  • писать в порядке then, catch, finally
  • использовать функции, которые возвращают промис

Promise chain

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
  alert(result); // 1
  return result * 2;
}).then(function(result) { // (***)
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});

Promise chain

.then возвращает новый Promise

Статические методы

  • Promise.resolve
  • Promise.reject
  • Promise.all - ошибка в одном -> ошибка у всего Promise
  • Promise.allSetteled - выполнятся все промисы, в then будет объект с status, value, reason. Никогда не падает с ошибкой
  • Promise.race - кто раньше(не важно resolve,reject). Статус будет соответствующий

Пример

const queue = [
  new Promise((resolve, reject) => {
  // run test
}),
  new Promise((_, reject) => {
  setTimeout(() => {
    reject('time is out!')
  }, 60_000)
})
]

Promise.race(queue)
  .then(() => {
  	// tests result
  })
  .catch((err) => {
  	console.error(err) // time is out?
  })

Промисификация

Процесс превращения кода на колбеках в промис

function fetchResource(url, callback){ /** реализация */}

function fetchResourceAsync(url) {/** new Promise*/}

fetchResourceAsync('res-1')
  .then(() => fetchResourceAsync('res-2'))
  .then(() => fetchResourceAsync('res-3'))

Промисификация в node.js

Используется встроенный модуль node:util

import util from 'node:util';
import fs from 'node:fs';

const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
}); 

async/await

используется только для функции

async function f() {
  return 1;
}

f().then(res => console.log(res)) // 1

async/await

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("готово!"), 1000)
  });

  let result = await promise; // будет ждать (*)

  console.log(result); // "готово!"
}

f();

await работает только в асинхронных функциях

async/await

Обработка ошибок

async function f() {
  await Promise.reject(new Error("Упс!"));
}
async function f() {
  throw new Error("Упс!");
}

Обработка ошибок

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    console.log(err); // TypeError: failed to fetch
  }
}

f();

Выводы

  • async/await = Promise/then
  • обработка ошибок такая же как и в синхронных функциях - try/catch/finally
  • сборщиками преобразуется в Promise

References

05. Planning & Async

By vitalic gorodkov

05. Planning & Async

  • 156