Могут ли несколько вызывающих абонентов подписаться на одну и ту же асинхронную функцию, используя ключевое слово await в Babel?

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

let curWaitTask;
let lastWaitTime = new Date(1970, 0, 1);
let func1 = function() {
  if(curWaitTask) {
    return curWaitTask;
  }
  if((new Date() - lastWaitTime) > TWENTY_MINUTES) {
    lastWaitTime = new Date();
    curWaitTask = func2().then(func3).then(func4);
    return curWaitTask;
  }
}

Выше приведен подход с истекающим сроком действия кеша. Первый вызов func1 после истечения 20-минутного кэша заставит службу выполнить еще одну асинхронную операцию для обновления кэша. В то же время могут возникнуть другие вызовы func1, которые будут ожидать одной и той же операции обновления кеша, а не повторяться снова и снова, пока кеш не будет установлен (несколько раз подряд из-за всех вызовов).

Есть ли способ получить такое поведение, используя ясность, предлагаемую ключевым словом Babel await, вместо строки громоздких .then()? На практике становится некрасиво.


person Brandon Arnold    schedule 20.10.2016    source источник
comment
Я не уверен, что понимаю вопрос. Вы не можете просто написать result = await operation()?   -  person Casey    schedule 20.10.2016
comment
@Casey: Скажем, operation() занимает много времени, например, 1 минуту. Если строка await operation() вызывается 10 раз за 5-секундный период, она будет выполнять весь 1-минутный вызов 10 раз. Но в приведенном мной примере func1 длинная цепочка func2.then(func3).then(func4) происходит только в первый раз, а следующие 9 вызовов func1() будут ждать результата первого промиса. Увидеть разницу?   -  person Brandon Arnold    schedule 20.10.2016
comment
Я понимаю. Но разве вы не можете просто ввести curwaittask = await func1(), а затем зациклить?   -  person Casey    schedule 20.10.2016
comment
lastWaitTime когда-либо обновлялся? И что должен делать вызов func1, когда нет ни кэшированного значения, ни истечения времени ожидания?   -  person Bergi    schedule 20.10.2016
comment
Вы, конечно, имели в виду func2(), а не func2?   -  person Bergi    schedule 20.10.2016
comment
@Bergi: Спасибо, хороший улов на func2(). Предполагалось, что набор lastWaitTime произойдет в пределах func4, но я установил его выше для ясности.   -  person Brandon Arnold    schedule 20.10.2016
comment
@BrandonArnold Спасибо, я обновил свой ответ. Установка lastWaitTime в func4 vs перед цепочкой then приводит к некоторой разнице во времени, но не меняет ответ. Тем не менее, вы так и не ответили на вопрос, что должно произойти, когда curWaitTask нет, а срок lastWaitTime еще не истек?   -  person Bergi    schedule 20.10.2016
comment
@BrandonArnold Кроме того, curWaitTask никогда не устанавливается или значение остается там навсегда?   -  person Bergi    schedule 20.10.2016
comment
@Bergi: Спасибо, я думаю, мне нравится ваш ответ ниже, сейчас я его просматриваю. Предполагается, что никогда не возникает условие, что curWaitTask равно нулю, за исключением первой итерации. Возможно, цепочка .then может генерировать или возвращать null, но я не думаю, что это делает проблему более понятной для учета этих ошибок. Просто иллюстрирую метод.   -  person Brandon Arnold    schedule 20.10.2016
comment
Теперь я вижу смысл вашего вопроса. Я печатаю на своем телефоне, мне придется просмотреть сегмент кода, из которого я скопировал это, чтобы увидеть, что у меня не так.   -  person Brandon Arnold    schedule 20.10.2016


Ответы (2)


Просто оберните «громоздкую цепочку then» в async function и используйте там await:

let curWaitTask;
let lastWaitTime = new Date(1970, 0, 1);
function func1() {
  if (!curWaitTask && (new Date() - lastWaitTime) > TWENTY_MINUTES) {
    lastWaitTime = new Date();
    curWaitTask = doTask();
  }
  return curWaitTask;
}
async function doTask() {
  return func4(await func3(await func2()));
}

Внутри async function невозможно (к счастью?) ссылаться на новое обещание, созданное в результате текущего вызова, поэтому использование async function func1 здесь не очень разумно.

person Bergi    schedule 20.10.2016

Вы спрашивали, могут ли несколько вызывающих абонентов подписаться на одну и ту же асинхронную функцию, поэтому давайте посмотрим, сможем ли мы решить эту проблему в рамках func1 и сохранить ее асинхронной (с помощью Берги!):

let curTask;
let lastTime = new Date(1970, 0, 1);

async function func1() {
  if (curTask && (new Date() - lastTime) < 10000) {
    return await curTask;
  }
  return await (curTask = (async () => {
    lastTime = new Date();
    return await func4(await func3(await func2()));
  })());
}

Мы можем ждать промисов, а не только асинхронных функций.

Мы помещаем основную часть работы во вторую асинхронную функцию, которую не ожидаем немедленно, тем самым получая вместо нее обещание, которым мы инициализируем curTask синхронно.

person jib    schedule 20.10.2016
comment
Такое использование new Promise и resolve/reject весьма сомнительно и чревато ошибками. Я бы скорее рекомендовал использовать async немедленно вызываемое функциональное выражение, если вам действительно нужно что-то подобное. - person Bergi; 21.10.2016
comment
@Bergi Сомнительно, но подвержено ошибкам? Он в значительной степени имитирует шаблон конструктора обещаний внутри асинхронной функции, за исключением того, что я не передаю настоящую асинхронную функцию конструктору обещаний... Может быть, мне стоит попробовать... - person jib; 21.10.2016
comment
Подвержен ошибкам по трем причинам: вы можете забыть try/catch (особенно внутри new Promise), а не reject, когда это уместно, вы вы можете забыть resolve обещание (но вместо этого попробуйте return), или вы можете случайно попытаться await curTask перед вызовом resolve. Поэтому практическое правило: не используйте async function внутри конструктора промисов. Просто сделайте curTask = (async () => { …; return …; }()). - person Bergi; 21.10.2016
comment
@Bergi Вы правы, что в основном выполняет то же самое, но проще. Сладкий, спасибо! - person jib; 21.10.2016