Работа с Promise в Angular приложениях

В Angular-приложениях взаимодействие с асинхронными операциями встречается повсеместно: HTTP-запросы, таймеры, обработка событий, обращение к локальному хранилищу и многое другое. В контексте автоматизированного тестирования с использованием Protractor правильная работа с Promise критически важна для стабильности и предсказуемости тестов.

Основы Promise в JavaScript

Promise — это объект, представляющий результат асинхронной операции, который может находиться в одном из трёх состояний:

  • pending — операция ещё не завершена;
  • fulfilled — операция успешно завершена, доступен результат;
  • rejected — операция завершена с ошибкой.

Создание промиса выполняется через конструктор:

let promise = new Promise((resolve, reject) => {
    // Асинхронная операция
    if (успех) {
        resolve(result);
    } else {
        reject(error);
    }
});

Обработка результата осуществляется с помощью методов .then() и .catch():

promise
    .then(result => console.log('Результат:', result))
    .catch(error => console.error('Ошибка:', error));

Промисы в контексте Protractor

Protractor — это инструмент для end-to-end тестирования Angular-приложений, построенный поверх WebDriverJS. Он использует Promise для работы с асинхронными действиями браузера.

Все методы Protractor, возвращающие элементы или выполняющие действия с ними, возвращают промисы. Например:

let button = element(by.css('.submit-button'));
button.click(); // Возвращает Promise

Для корректного выполнения последовательности действий промисы необходимо обрабатывать:

button.click()
    .then(() => {
        return element(by.css('.result')).getText();
    })
    .then(text => {
        console.log('Текст после клика:', text);
    });

Управление асинхронностью в Angular через async/await

Использование async/await позволяет писать асинхронный код в стиле синхронного, что делает тесты более читаемыми:

it('должен получить текст после клика', async () => {
    let button = element(by.css('.submit-button'));
    await button.click();

    let result = await element(by.css('.result')).getText();
    console.log('Результат:', result);
});

Ключевой момент: await работает только с объектами, реализующими интерфейс Promise. Все методы Protractor можно использовать с await.

Проблемы с синхронизацией и Angular

Angular активно использует зону (zone.js) для отслеживания асинхронных операций. Protractor автоматически ждет завершения всех задач Angular перед выполнением следующего шага, что снижает вероятность «гонок» (race conditions).

Однако существуют ситуации, когда ожидание нужно реализовать вручную:

  • Работа с нестандартными элементами, не управляемыми Angular;
  • Внешние скрипты или сторонние виджеты;
  • Таймауты AJAX-запросов, выполняемых вне Angular.

Для этого применяются Explicit Waits:

let EC = protractor.ExpectedConditions;
let elementToWait = element(by.css('.dynamic-element'));

browser.wait(EC.visibilityOf(elementToWait), 5000, 'Элемент не появился');

Метод browser.wait возвращает Promise, который резолвится после выполнения условия или отклоняется по таймауту.

Последовательные и параллельные промисы

Часто в тестах возникает необходимость выполнять несколько асинхронных операций:

Последовательно:

await element(by.css('.input')).sendKeys('тест');
await element(by.css('.submit')).click();
let text = await element(by.css('.result')).getText();

Параллельно с Promise.all:

let elements = [element1.getText(), element2.getText(), element3.getText()];
let results = await Promise.all(elements);
console.log('Результаты всех элементов:', results);

Promise.all завершится, когда все промисы будут выполнены. Если один промис отклоняется, весь Promise.all будет отклонён.

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

Ошибки асинхронных операций нужно обрабатывать, иначе тест может завершиться неконтролируемым исключением.

Использование try/catch с async/await:

try {
    await element(by.css('.submit')).click();
    let text = await element(by.css('.result')).getText();
    console.log(text);
} catch (error) {
    console.error('Ошибка при выполнении теста:', error);
}

При цепочках .then() ошибки можно ловить через .catch():

element(by.css('.submit')).click()
    .then(() => element(by.css('.result')).getText())
    .then(text => console.log(text))
    .catch(error => console.error('Ошибка:', error));

Интеграция с ожиданиями Angular

Protractor предоставляет специальный метод browser.waitForAngular() для ручного ожидания завершения всех асинхронных операций Angular:

await browser.waitForAngular();
let data = await element(by.css('.data')).getText();

Это особенно важно при тестировании страниц, где Angular-операции инициируются динамически или после AJAX-запросов.

Лайфхаки при работе с промисами

  • Всегда использовать async/await вместо цепочек .then(), если тест содержит более двух шагов.
  • Для массивов элементов удобно применять map с Promise.all, чтобы собирать результаты одновременно.
  • Не использовать sleep без необходимости; предпочтительнее ExpectedConditions.
  • Для дебага промисов можно временно использовать .then(console.log) перед основным действием.

Особенности работы с Protractor 7+

Начиная с Protractor 7 и выше, управление контролом синхронизации стало более явным. Автоматическое ожидание Angular (control flow) отключено, что делает async/await обязательным для большинства тестов.

browser.waitForAngularEnabled(false); // для не-Angular страниц

Это важно учитывать при интеграции тестов с приложениями, где часть страниц не управляется Angular.


Работа с промисами в Angular и Protractor требует аккуратного обращения с асинхронностью, грамотного использования async/await и контроля над ожиданиями. Понимание этих принципов обеспечивает стабильные и предсказуемые end-to-end тесты, минимизируя ошибки синхронизации и «гонок» между шагами тестов.