Розуміння конкурентності в JavaScript та AWS Lambda

Конкурентність є критично важливою для сучасної розробки на JavaScript, особливо при створенні масштабованих безсерверних додатків. У цій статті ми розглянемо модель конкурентності JavaScript, найкращі практики структурування асинхронного коду та особливості, специфічні для середовищ виконання AWS Lambda.

Модель конкурентності JavaScript

JavaScript працює як однонитковий (single-threaded) мову, керуючи конкурентністю через event loop (цикл подій) і message queue (чергу повідомлень). У будь-який момент часу в середовищі виконання JavaScript існує лише один стек викликів, який повинен бути очищений перед обробкою нових повідомлень. Кожне повідомлення з черги виконує колбек, потенційно додаючи нові повідомлення в чергу і продовжуючи цикл.

Ключові моменти:

  1. Уникайте блокуючого коду, такого як alert() у браузерах або fs.readFileSync() в Node.js, оскільки він зупиняє стек викликів.
  2. Звертайте увагу на те, як послідовно виконується асинхронний код, щоб підтримувати оптимальну продуктивність.

Приклад: Блокуючий код

function immediateRequest() {  
 return Promise.resolve(1)  
}  

function delayedRequest(delay) {  
 return new Promise((resolve) => {  
 setTimeout(() => resolve(1), delay)  
 })  
}  

async function blockingExample() {  
 immediateRequest().then(() => console.log('Immediate request resolved'))  
 delayedRequest(5000).then(() => console.log('Delayed request resolved'))  
 alert('Block') // Blocking operation  
 console.log('Blocking done')  
}

У цьому прикладі для браузера виконання зупиняється на лінії alert(), затримуючи вирішення інших асинхронних операцій.

Оптимізація послідовності Promise

Ефективне оброблення асинхронних операцій вимагає уникання послідовного виконання, коли це не обов'язково. Порівняємо наступні варіанти:

Послідовне виконання:

async function handleRequestA() {  
 const a = await request3()  
 const b = await request2()  
 const c = await request3()  
 const d = await request4()  
 return { a, b, c, d }  
}

Це займе близько 12 секунд, оскільки кожен проміс вирішується один за одним.

Паралельне виконання:

async function handleRequestB() {  
 const [a, b, c, d] = await Promise.all([  
 request3(),  
 request2(),  
 request3(),  
 request4(),  
 ])  
 return { a, b, c, d }  
}

Цей підхід вирішує всі проміси одночасно, зменшуючи час виконання до 4 секунд.

Async/Await vs Promises

Async/await є синтаксичним цукром для промісів, які, в свою чергу, абстрагують колбеки. Хоча async/await покращує читаність, він взаємодіє з конкурентністю JavaScript так само, як і проміси.

Приклад без синтаксичного цукру:

async function fnA() {  
 const a = await getA()  
 const b = await getB()  
 return { a, b }  
}  

// Еквівалентно:  
function fnB() {  
 return getA().then(a => getB().then(b => ({ a, b })))  
}

Версія без синтаксичного цукру показує, що getB() не виконується, поки не завершиться getA(), що підкреслює важливість структурування залежних і незалежних операцій.

Особливості AWS Lambda

У безсерверних середовищах, таких як AWS Lambda, конкурентність приносить додаткові міркування:

  1. Конкурентні запити: Багато викликів можуть оброблятися одним або різними середовищами виконання Lambda, що створює потенційні умови гонки при використанні спільних ресурсів.
    2.
    Непередбачуване виконання: Асинхронні операції, відокремлені від повернутого промісу обробника, можуть виконуватись непередбачувано, залежно від життєвого циклу середовища виконання.

Приклад: Поведінка середовища виконання

const handler = async (event) => {  
 const A = await getA()  
 const B = await getBfromA(A)  
 independentRequest().then(importantCallback)  
 return { A, B }  
}

Тут importantCallback може або не може виконатися до того, як обробник завершить свою роботу, і його поведінка залежить від повторного використання середовища виконання або його вимкнення.

Кращі практики структурування асинхронного коду

Незалежні проміси:

Не робити:

const handler = async (event) => {  
 const a = getA()  
 const b = getB()  
 const c = getC()  
 return { a, b, c }  
}

Робити:

const handler = async (event) => {  
 const [a, b, c] = await Promise.all([getA(), getB(), getC()])  
 return { a, b, c }  
}

Залежні проміси:

Для операцій, де проміси залежать один від одного, послідовне виконання є прийнятним:

const handler = async (event) => {  
 const a = await getA()  
 const b = await getBFromA(a)  
 const c = await getCFromB(b)  
 return { a, b, c }  
}

Глобальні змінні та кешування в Lambda

Середовище виконання AWS Lambda може повторно використовувати глобальні змінні між запитами. Хоча це може покращити продуктивність для незмінних даних або кешів, змінні, що можна змінювати, можуть викликати помилки через непередбачуване повторне використання.

Приклад: Незмінні глобальні змінні

const dbConnection = initializeDBConnection()  
const handler = async () => {  
 return dbConnection.query('SELECT * FROM users')  
}

Уникайте зміни глобальних змінних:

let mutableCounter = 0  
const handler = async () => {  
 mutableCounter++ // Непередбачувана поведінка між запитами  
 return { mutableCounter }  
}

Тестування функцій Lambda локально

Щоб тестувати функції Lambda локально:

  1. Створіть основну точку входу (index.js), яка імітує виклики Lambda.
  2. Змокуйте зовнішні залежності (наприклад, значення параметрів або з’єднання з базою даних).
  3. Переконайтесь, що конфігурації для тестування максимально наближені до виробничого середовища.
const { lambdaToTest } = require('./src/lambda')  

if (require.main === module) {  
 lambdaToTest({ key: 'value' })  
 .then(result => console.log(result))  
 .catch(err => console.error(err))  
}

Висновок

Конкурентність у JavaScript і AWS Lambda вимагає уважного ставлення до асинхронних операцій і поведінки середовища виконання. Розробники можуть створювати ефективні, надійні та масштабовані додатки, розуміючи нюанси моделі конкурентності JavaScript та застосовуючи кращі практики для структурування асинхронного коду.

Перекладено з: Understanding Concurrency in JavaScript and AWS Lambda

Leave a Reply

Your email address will not be published. Required fields are marked *