Node.js, возвращайте одно и то же обещание для всех вызывающих, пока обещание не будет разрешено

У меня есть сложная функция обещания, которая вызывается несколько раз с одними и теми же параметрами. Я хочу, чтобы избежать повторного запуска одних и тех же вычислений для одних и тех же параметров. Вместо этого, если последовательный вызов имеет те же параметры, вернуть обещание, созданное предыдущим.

Обычно для таких случаев я бы использовал пакет memoizee (или memoize в lodash). Однако это обещание возвращает большой набор данных, который может быть проблематично хранить в памяти. Поскольку эти пакеты кэшируют результаты в памяти, они не подходят для этого случая.

Упрощенный пример:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

async function sleepOneSec() {
  console.info("going to sleep");
  await sleep(1000)
};

async function myApp() {
  await Promise.all([1, 2, 3].map(async item => {
    console.info('item', item);
    await sleepOneSec();
  }));
  console.info('promise.all is done');
  await sleepOneSec();
  console.info('completed');
}

myApp().then(() => {}).catch(err => console.info(err));

в настоящее время без какого-либо запоминания вывод:

item 1
going to sleep
item 2
going to sleep
item 3
going to sleep
promise.all is done
going to sleep
completed

В идеале я хочу, чтобы результаты были:

item 1
going to sleep
item 2
item 3
promise.all is done
going to sleep
completed

Сохраните промис в какой-нибудь коллекции, где его можно найти через параметры. Прежде чем создавать новый промис с набором параметров, посмотрите в своем хранилище, нет ли уже промиса с этими параметрами. Если да, верните его, если нет создайте новый. Конечно, это сохранит результат обещания в памяти. Если вы этого не хотите, запишите результат в файл после разрешения промиса и верните readFile или аналогичный для последующих вызовов.

derpirscher 13.10.2022 00:01

Где функция с аргументами, где вы хотите объединить обещание, если аргументы одинаковы? Единственные функции, возвращающие обещания, которые вы показываете, — это sleep() и sleepOneSec(). Пожалуйста, покажите свой реальный код, а не какой-то вымышленный код, чтобы мы могли решить реальные проблемы, которые могут у вас возникнуть. Нам также необходимо знать, к какому типу данных относятся аргументы и, если они являются объектами, как именно их следует сравнивать на равенство.

jfriend00 13.10.2022 01:20

@derpirscher, спасибо, но это не поможет. Если я сохраняю результат обещания в этой коллекции, то практически я кэширую его в памяти, чего мне не нужно. Запись в файл занимает много времени и требует сериализации, что в моем случае очень сложно и нереалистично.

David 13.10.2022 01:57

@ jfriend00, настоящий код здесь слишком длинный, никто не станет его читать. Это на самом деле не очень важно. Если у вас есть решение, которое не обрабатывает параметры, это было бы здорово. Если параметры важны, вы можете заменить sleepOneSec() на sleepOneSec(num), который возвращает полученное число. Все вызовы этой функции передают один и тот же параметр: «1».

David 13.10.2022 02:01

Так что в основном вам не нужны ваши результаты в памяти, и вы не можете записать их в файл... У меня закончились идеи, где еще их хранить...

derpirscher 13.10.2022 09:01
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
5
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, вот реализация для sleepOneSec(num), которая генерирует требуемый результат. Он создает кеш для промиса от sleepOneSec(), который индексируется аргументами. Обещание удаляется из кеша, как только оно разрешается или отклоняется, поэтому оно повторно используется только во время «выполнения»:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const sleepCache = new Map();

async function sleepOneSec(num) {
    // serialize args
    const args = JSON.stringify({ num });
    let p = sleepCache.get(args);
    if (!p) {
        console.info("going to sleep");
        p = sleep(num);
        sleepCache.set(args, p);

        // when this promise resolves, remove it from the cache
        // so it won't be used any more
        const remove = () => sleepCache.delete(args);
        p.then(remove, remove);
    }
    return p;
}

async function myApp() {
    await Promise.all([1, 2, 3].map(async item => {
        console.info('item', item);
        await sleepOneSec(1000);
    }));
    console.info('promise.all is done');
    await sleepOneSec(1000);
    console.info('completed');
}

myApp().then(() => {}).catch(err => console.info(err));

Если аргументы рассматриваемой функции содержат объекты, то вы должны убедиться, что они однозначно сериализуемы с помощью JSON.stringify(), или вам нужно вручную преобразовать их во что-то, что представляет их уникальность и сериализуемо с помощью JSON.stringify(). Также важно вручную обрабатывать сериализацию любых объектов, потому что JSON.stringify() не обязательно приводит свойства в строку в том же порядке, который нарушит сравнение. Вот почему я спросил об аргументах в вашем реальном коде. Все, что вы предложили мне, когда я спросил, каковы настоящие аргументы, это один числовой аргумент, вот что показано здесь. Но если у вас есть объекты в качестве аргументов, с ними нужно обращаться осторожно, чтобы кэширование работало правильно.

Фантастический! Именно то, что я искал. Большое спасибо за подробный ответ! Я не уверен, что здесь второе remove: p.then(remove, remove); Думаю, это может быть: p.then(remove);. Что касается параметров, я полностью понимаю (я делал это много раз раньше, поэтому не хотел усложнять здесь). Еще раз спасибо! :)

David 13.10.2022 15:34

@David - promise.then(resolveHandler, rejectHandler) принимает два аргумента (второй необязателен). Если предоставлен, второй аргумент — это rejectHandler. Вы должны убедиться, что вы удалили обещание из кеша, когда обещание разрешено ИЛИ отклонено.

jfriend00 13.10.2022 22:37

О, это верно. Я привык к синтаксису .then().catch(). Спасибо за разъяснение.

David 14.10.2022 17:36

Другие вопросы по теме