DevGang
Авторизоваться

Понимание условий асинхронной гонки Javascript 

Термин «состояние гонки» обычно применяется к конфликту при доступе к общим переменным в многопоточной среде. В Javascript ваш JS-код выполняется только одним потоком за раз, но все же можно создавать похожие проблемы.

Это распространенная проблема, когда люди просто вслепую делают свои функции асинхронными, не задумываясь о последствиях.

Возьмем очень простой пример — ленивая загрузка какого-то ресурса с одним экземпляром.

Синхронная версия проста:

let res;
function get_resource() {
    if(!res) res = init_resource();
    return res;
}

Асинхронная версия:

let res;
async function get_resource() {
    if(!res) res = await init_resource();
    return res;
}

Представьте get_resource(), что вас вызывают на веб-сервер при каждом запросе. Если между первым и вторым запросом проходит достаточно времени, все будет работать нормально. Но что произойдет, если вы получите больше запросов, в то время как первый все еще ждет ресурс?

Это может привести к серьезным проблемам, которые очень трудно устранить.

Другие примеры

Вот несколько примеров (из этой ветки HN):

Баланс:

    async function deduct(amt) {
        var balance = await getBalance();
        if (balance >= amt)
            return await setBalance(balance - amt);
    }

И более тонкий пример:

  async function totalSize(fol) {
    const files = await fol.getFiles();
    let totalSize = 0;
    await Promise.all(files.map(async file => {
      totalSize += await file.getSize();
    }));
    // totalSize is now way too small
    return totalSize;
  }

Возможные решения

Лучший способ избежать подобных проблем — избегать асинхронных функций там, где это не является абсолютно необходимым (см.: [[Функциональное ядро, императивная оболочка]]).

Если это невозможно, вы можете рассмотреть возможность использования Mutex (из взаимного исключения) — как только кто-то получит блокировку, другие запросы будут заблокированы до тех пор, пока первоначальный держатель не освободит блокировку.

Т.е. с пакетом async-mutex наш предыдущий пример может выглядеть так:

let res;
async function get_resource() {
    await mutex.runExclusive(async () => {
        if(!res) res = await init_resource();
    });
    return res;
}

async-mutex также поддерживает семафоры - это аналогичная концепция, но можно получить несколько блокировок.

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В подарок 100$ на счет при регистрации

Получить