Использование then/catch с методами

Библиотека localForage полностью построена вокруг асинхронной модели работы с данными. Практически каждый её метод возвращает объект Promise, благодаря чему операции чтения, записи, удаления и перебора данных выполняются без блокировки основного потока браузера.

До широкого распространения синтаксиса async/await основным способом обработки результатов асинхронных операций была цепочка методов then() и catch(). Несмотря на популярность современных конструкций, данный подход по-прежнему активно используется в существующих проектах, библиотеках и фреймворках.

Методы then() и catch() позволяют:

  • получать результат успешного выполнения операции;
  • выполнять последовательность асинхронных действий;
  • централизованно обрабатывать ошибки;
  • строить длинные цепочки операций над данными;
  • избегать вложенности колбэков.

Promise в localForage

Практически любой метод localForage возвращает Promise:

localforage.setItem("user", {
    name: "Alex"
});

На первый взгляд кажется, что запись выполняется мгновенно. На самом деле метод немедленно возвращает Promise, а сама операция сохранения завершается позже.

Упрощённо это выглядит следующим образом:

const promise = localforage.setItem("user", {
    name: "Alex"
});

console.log(promise);

В консоли можно увидеть объект Promise, который находится в одном из состояний:

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

Именно для обработки этих состояний используются методы then() и catch().


Использование then() после setItem()

После успешного сохранения данных вызывается функция, переданная в then().

Пример:

localforage
    .setItem("username", "admin")
    .then(function(value) {
        console.log("Сохранено:", value);
    });

Результат:

Сохранено: admin

Особенность метода setItem() заключается в том, что в then() возвращается сохранённое значение.

Это позволяет сразу использовать полученный объект:

localforage
    .setItem("settings", {
        theme: "dark",
        language: "ru"
    })
    .then(function(settings) {
        console.log(settings.theme);
    });

Вывод:

dark

Использование then() после getItem()

При чтении данных в обработчик передаётся найденное значение.

Пример:

localforage
    .getItem("username")
    .then(function(value) {
        console.log(value);
    });

Если запись существует:

admin

Если ключ отсутствует:

null

Поэтому часто выполняют дополнительную проверку:

localforage
    .getItem("username")
    .then(function(value) {

        if (value === null) {
            console.log("Пользователь не найден");
            return;
        }

        console.log(value);

    });

Использование catch() для обработки ошибок

Метод catch() вызывается при возникновении ошибки в Promise.

Общий шаблон:

localforage
    .getItem("user")
    .then(function(value) {
        console.log(value);
    })
    .catch(function(error) {
        console.error(error);
    });

Если операция завершилась успешно, catch() не будет вызван.

Если произошла ошибка, управление перейдёт в него автоматически.


Цепочка then()

Одним из главных преимуществ Promise является возможность создавать цепочки операций.

Пример:

localforage
    .setItem("counter", 10)
    .then(function() {
        return localforage.getItem("counter");
    })
    .then(function(value) {
        console.log(value);
    });

Порядок выполнения:

  1. Сохраняется значение.
  2. После завершения начинается чтение.
  3. Полученное значение выводится в консоль.

Результат:

10

Передача данных между then()

Каждый then() может возвращать значение, которое автоматически попадёт в следующий обработчик.

Пример:

localforage
    .getItem("user")
    .then(function(user) {

        return user.name;

    })
    .then(function(name) {

        console.log(name);

    });

Если в хранилище находится объект:

{
    name: "Alex",
    age: 30
}

то вывод будет:

Alex

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


Возврат нового Promise из then()

Наиболее распространённый сценарий — запуск следующей асинхронной операции.

Пример:

localforage
    .getItem("user")
    .then(function(user) {

        return localforage.getItem("settings");

    })
    .then(function(settings) {

        console.log(settings);

    });

Когда из then() возвращается Promise, следующий обработчик автоматически ожидает его завершения.

Это называется «сплющиванием» цепочки Promise.


Последовательное выполнение нескольких операций

Рассмотрим последовательную работу с несколькими ключами.

localforage
    .setItem("name", "Alex")
    .then(function() {

        return localforage.setItem("age", 30);

    })
    .then(function() {

        return localforage.setItem("city", "Moscow");

    })
    .then(function() {

        console.log("Все данные сохранены");

    })
    .catch(function(error) {

        console.error(error);

    });

Каждая операция начинается только после завершения предыдущей.


Обработка ошибок в цепочке

Любая ошибка внутри цепочки автоматически передаётся в ближайший catch().

Пример:

localforage
    .getItem("user")
    .then(function(user) {

        console.log(user.name);

    })
    .catch(function(error) {

        console.error("Ошибка:", error);

    });

Если значение окажется равным null, попытка обращения к свойству может привести к исключению.

Ошибка будет перехвачена:

Ошибка: TypeError

Генерация собственных ошибок

Иногда необходимо самостоятельно прервать выполнение цепочки.

Пример:

localforage
    .getItem("user")
    .then(function(user) {

        if (!user) {
            throw new Error("Пользователь отсутствует");
        }

        return user;

    })
    .then(function(user) {

        console.log(user.name);

    })
    .catch(function(error) {

        console.error(error.message);

    });

Вывод:

Пользователь отсутствует

Использование нескольких then() подряд

Количество обработчиков не ограничено.

Пример:

localforage
    .getItem("price")
    .then(function(price) {

        return price * 1.2;

    })
    .then(function(priceWithTax) {

        return Math.round(priceWithTax);

    })
    .then(function(result) {

        console.log(result);

    });

Каждый этап отвечает за отдельное преобразование данных.

Такой подход делает код более читаемым и поддерживаемым.


Возврат значения по умолчанию после ошибки

После обработки ошибки цепочка может продолжиться.

Пример:

localforage
    .getItem("settings")
    .catch(function() {

        return {
            theme: "light"
        };

    })
    .then(function(settings) {

        console.log(settings.theme);

    });

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


Использование второго аргумента then()

Метод then() может принимать два обработчика:

localforage.getItem("user").then(
    function(result) {
        console.log(result);
    },
    function(error) {
        console.error(error);
    }
);

Однако такой стиль считается менее удобным.

Гораздо чаще используется конструкция:

localforage
    .getItem("user")
    .then(function(result) {
        console.log(result);
    })
    .catch(function(error) {
        console.error(error);
    });

Она обеспечивает более понятную структуру и лучше подходит для длинных цепочек.


Работа с removeItem()

Удаление данных также возвращает Promise.

Пример:

localforage
    .removeItem("token")
    .then(function() {

        console.log("Токен удалён");

    })
    .catch(function(error) {

        console.error(error);

    });

Поскольку метод ничего не возвращает, параметр обработчика обычно не используется.


Работа с clear()

Полная очистка хранилища выполняется аналогичным образом.

localforage
    .clear()
    .then(function() {

        console.log("Хранилище очищено");

    })
    .catch(function(error) {

        console.error(error);

    });

Очистка считается завершённой только после успешного выполнения Promise.


Получение количества записей

Метод length() возвращает Promise с числом элементов.

localforage
    .length()
    .then(function(count) {

        console.log(count);

    });

Результат:

15

Полученное число можно использовать в последующих обработчиках:

localforage
    .length()
    .then(function(count) {

        return count * 2;

    })
    .then(function(result) {

        console.log(result);

    });

Комбинирование нескольких запросов

Для выполнения нескольких операций одновременно часто применяется Promise.all().

Пример:

Promise.all([
    localforage.getItem("user"),
    localforage.getItem("settings"),
    localforage.getItem("token")
])
.then(function(values) {

    console.log(values);

})
.catch(function(error) {

    console.error(error);

});

В массиве values будут находиться результаты всех запросов:

[
    user,
    settings,
    token
]

Все Promise должны завершиться успешно. Если хотя бы один завершится ошибкой, управление перейдёт в catch().


Типичные ошибки при использовании then() и catch()

Отсутствие return внутри цепочки

Неправильно:

localforage
    .getItem("user")
    .then(function() {

        localforage.getItem("settings");

    })
    .then(function(settings) {

        console.log(settings);

    });

Следующий обработчик не дождётся завершения операции.

Правильно:

localforage
    .getItem("user")
    .then(function() {

        return localforage.getItem("settings");

    })
    .then(function(settings) {

        console.log(settings);

    });

Игнорирование ошибок

Нежелательно:

localforage
    .setItem("data", value)
    .then(function() {

        console.log("OK");

    });

Предпочтительно:

localforage
    .setItem("data", value)
    .then(function() {

        console.log("OK");

    })
    .catch(function(error) {

        console.error(error);

    });

Смешивание синхронной и асинхронной логики

Неправильно:

const value = localforage.getItem("user");

console.log(value);

В переменной окажется Promise, а не данные.

Правильно:

localforage
    .getItem("user")
    .then(function(value) {

        console.log(value);

    });

Когда использование then/catch особенно полезно

Наиболее уместно применение цепочек Promise в следующих ситуациях:

  • поддержка старых проектов, написанных до появления async/await;
  • создание последовательных сценариев обработки данных;
  • выполнение нескольких связанных операций над хранилищем;
  • централизованная обработка ошибок;
  • работа с библиотеками и API, которые активно используют Promise;
  • построение сложных асинхронных цепочек без вложенных колбэков.

Методы then() и catch() являются фундаментальным механизмом взаимодействия с localForage, поскольку позволяют полностью контролировать жизненный цикл асинхронных операций хранения данных и обеспечивают предсказуемую обработку как успешных результатов, так и возникающих ошибок.