Еволюція асинхронного JavaScript


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Функції async практично тут, але дорога до них була довгою. Не так давно ми писали колбеки, потім з’явилися Promise / A+ специфікації, слідом за ними – функції-генератори, і тепер async-функції.

Давайте озирнемося назад і подивимося на те, як асинхронний JavaScript розвивався з роками.

Колбеки

Все розпочалося з колбеків.

Асинхронний JavaScript

Асинхронне програмування, яким ми його знаємо в JavaScript, може бути реалізоване тільки функціями, що є першочерговими членами мови: вони можуть бути передані як будь-яка змінна іншим функціям. Так народилися колбеки: якщо Ви передаєте функцію іншій функції (функції вищого порядку) як параметр, у межах функції Ви можете викликати її, коли закінчите виконання своєї задачі. Жодних повернених значень, Ви просто викликаєте іншу функцію з параметрами.

Something.save (function (err) { if (err) { //обробка помилок return; } console.log ('success');});

Ці так звані error-first колбеки лежать у серці Node.js – модулі ядра використовують їх так само, як і більшість модулів, які Ви можете знайти в NPM.

Проблеми з колбеками:

  • легко написати “callback hell” або спагеті-код, якщо не використовувати їх належним чином;
  • легко пропустити обробку помилок;
  • не можна повертати значення з виразом return, як і не можна використати ключове слово throw.

В основному через це JavaScript-світ став шукати рішення, які зробили б асинхронну JavaScript-розробку легшою.

Однією з відповідей був модуль async. Якщо Ви багато працювали з колбеками, то знаєте, як складно може бути змусити щось, працююче паралельно, працювати послідовно; навіть мапінг масивів використовує асинхронні функції. Завдяки Каолану Макмейхону (Caolan McMahon) народився модуль async.

З async Ви можете просто робити так:

async.map ([1, 2, 3], AsyncSquaringLibrary.square, function (err, result){ // результатом буде [1, 4, 9] });

Promises

Поточна специфікація JS Promises (у перекладі з англійської – обіцянки) бере початок в 2012 і доступна з ES6, проте обіцянки не були розроблені співтовариством JavaScript. Термін ввів в обіг Деніел Фрідман у 1976 році.

Promise є кінцевим результатом асинхронної операції.

Попередній приклад з використання обіцянок виглядав би так:

Something.save ().then (function (){ console.log ('success'); })
.catch (function (){ //обробка помилки })

Ви можете помітити, що обіцянки також використовують колбеки. Як then, так і catch реєструють колбеки, які будуть викликані як при успішному завершенні асинхронної операції, так і тоді, коли вона з якихось причин не буде виконана. Інший плюс Promises полягає в тому, що вони можуть бути пов’язані ланцюжком:

saveSomething ().then (updateOtherthing) .then (deleteStuff) .then (logResults);

При використанні обіцянок Ви можете використати поліфіли в середовищах виконання, в яких їх ще немає. Популярний вибір у таких випадках – bluebird. Ці бібліотеки можуть надавати набагато більшу функціональність, ніж нативні обіцянки, але навіть у цих випадках слідуйте специфікаціям Promises/A+.

Ви можете запитати: “Як я можу використати обіцянки, коли більшість бібліотек надають тільки колбек-інтерфейси?”.

Це легко: єдина річ, яку Ви повинні зробити, – обернути колбек у функцію, що повертає обіцянку:

function saveToTheDb (value) { return new Promise (function (resolve, reject){ db.values.insert (value, function (err, user){ // помилка йде першою if (err){ return reject (err); // не забудьте повернути тут } resolve (user); }) }}

Деякі бібліотеки/фреймворки вже підтримують інтерфейси і колбеки, і обіцянки. Якщо Ви створюєте бібліотеку, корисною практикою буде підтримка обох. Ви легко можете зробити щось подібне до:

function foo (cb){ if (cb){ return cb (); } return new Promise (function (resolve, reject){ });}

Навіть простіше, Ви можете можете розпочати з інтерфейсів обіцянок і надати сумісність з такими інструментами, як callbackify. Callbackify в основному робить ті самі речі, що й попередній фрагмент коду, але загальнішим шляхом.

Генератори / yield

Генератори JavaScript – це відносно новий концепт, який був представлений в ES6.

Хіба не було б чудовим, якби при виконанні функції Ви могли б призупиняти її в якійсь точці, розраховувати щось, робити інші речі, а потім повертатися до неї, можливо, з якимось значенням, і продовжувати її виконання?

Це саме те, що функції-генератори роблять для Вас. Коли ми викликаємо функцію-генератор, вона не починає виконуватися, ми будемо повинні ітерирувати її вручну.

function* foo (){ var index = 0; while (index < 2) { yield index++; }}var bar = foo ();console.log (bar.next()); // { value: 0, done: false }console.log (bar.next()); // { value: 1, done: false }console.log (bar.next()); // { value: undefined, done: true }

Якщо Ви хочете легко використати генератори для написання асинхронного JavaScript, то Вам також знадобиться пакет co.

Із co наші попередні приклади могли б виглядати так:

co (function* (){ yield Something.save ();}).then (function (){ // success}).catch (function (err) { //error handling});

Ви можете запитати: “А що з операціями, які виконуються паралельно?”. Відповідь простіша, ніж Ви могли б подумати (під цією обгорткою лише Promise.all) :

yield [Something.save (), Otherthing.save ()];

Async / await

Async-функції були представлені в ES7, і нині доступні тільки з транспайлерами, такими як babel (зараз ми говоримо про ключове слово async, а не про пакет async).

Двома словами, з ключовим словом async можемо робити те, що ми робили з комбінацією co і генераторами, крім хаків.

Під капотом у async функції обіцянки, тому async-функція повертає тип Promise.

Якщо ми хочемо зробити те саме, що і в попередніх прикладах, то маємо переписати наш код:

async function save (Something) { try { await Something.save ()} catch (ex) { //error handling } console.log ('success');}

Як можете бачити, щоб використати async-функцію, Ви повинні поставити ключове слово async перед оголошенням функції. Після цього можете побачити ключове слово await усередині створеної async-функції.

Паралелізм async-функцій схожий з yield- підходом – за винятком того, що Promise.all не прихована, але Ви повинні викликати її:

async function save (Something) { await Promise.all[Something.save (), Otherthing.save ()]}

Koa вже підтримує async-функції, так що Ви можете випробувати їх сьогодні, використовуючи babel.

import koa from koa;let app = koa ();app.experimental = true;
app.use (async function (){ this.body = await Promise.resolve ('Hello Reader!')}) app.listen (3000);

За переклад дякуємо нашій підписниці, Олександрі Хісматуліній

Переклад статті “The Evolution of Asynchronous JavaScript”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *