-1

Есть простая как грабли задача. Три операции - две синхронные, между ними одна асинхронная. Нужно чтобы асинхронная задача выполнилась второй по счёту, а не последней. Конечный результат вычислений должен быть равен единице - 1. Как этого достичь используя синтаксис async/await, не используя глобальный объект Promise. Ниже примерное решение, НО неверное.

Зачем мне это нужно, что это за странный код, какой в этом смысл и т.д. эти вопросы сразу мимо. Я потратил на эту задачу несколько дней и все к кому я обращался не смогли её решить. Только ради этой задачи я зарегался на этом сайте, так что для меня это важно.

const summarize = (num1, num2) => num1 + num2;
const divide = (num1, num2) => num1/num2;

let x = 2; let y = 6; let z = 10;

const calcAsync = async () => { x = summarize(x, x); // (1шаг) 2 + 2 = 4 x = await setTimeout(() => summarize(x, y), 1);// (2шаг) ??? должно получится 4 + 6 = 10 x = divide(x, z); // (3шаг) ??? должно получится 10/10 = 1 }

calcAsync();

EzioMercer
  • 6,178
  • 2
  • 9
  • 28
  • Если я правильно помню, то в setTimeout передают только имя функции, если указаны ещё и скобки с аргументами, функция срабатывает сразу, без задержек. – Quazimorda Jun 27 '22 at 10:58
  • не используя глобальный объект Promise - никак. – Grundy Jun 28 '22 at 08:38

3 Answers3

7

Вам надо было обернуть setTimeout в Promise:

const summarize = (num1, num2) => num1 + num2;
const divide = (num1, num2) => num1 / num2;

let x = 2; let y = 6; let z = 10;

const calcAsync = async() => { x = summarize(x, x);

console.log(1, x);

x = await new Promise((resolve, reject) => { setTimeout(() => resolve(summarize(x, y))); });

console.log(2, x);

x = divide(x, z);

console.log(3, x); }

calcAsync();

Объяснение того, почему нельзя обойтись без Promise:

P.S. Формально это утверждение - не верное. Как показал @Grundy достаточно заменить Promise на самый обычный thenable объект (объект у которого есть метод then) и в этом случае всё будет работать точно так же как и с Promise. Но этот ответ даст вам понимание того, почему не поможет await-тить или оборачивать в async ф-ию setTimeout

Рассмотрим этот кусок кода:

const calcAsync = async () => {
  x = summarize(x, x);
  x = await setTimeout(() => summarize(x, y), 1);
  x = divide(x, z);
}

Т.к. async/await возвращает и работает Promise, то давайте переведём этот кусок кода на язык Promise-ов:

const calcAsync = () => {
  return Promise.resolve()
    .then(function () {
      x = summarize(x, x);
  return setTimeout(() => summarize(x, y), 1);
})
.then(function (handleResolved) {
  x = handleResolved;
  x = divide(x, z);
});

};

И так нас интересует строка с setTimeout. В документации сказано, что then на вход получит от Promise-а 2 аргумента - это handleResolved и handleRejected и так же сказано, что он сам тоже вернёт Promise. Нас интересует, только handleResolved, потому дальше handleRejected будет опущен. И так второй then на вход получит возращаемое значение setTimeout - это число timeoutID и абсолютно не важно что какая ф-ия будет запущена и когда она будет запущена. Следовательно во втором then, где строчка x = handleResolved в x будет записано число timeoutID и дальнейшие вычисления будут проводиться именно с этим значением. Что важно это число каждый раз будет разным, и перезапуская один и тот же код в одной сессии вы будете получать разные значения

Демо код (специально убрал x = await..., чтобы не подумали, что всё из-за того что в x что-то записывается):

const summarize = (num1, num2) => num1 + num2;
const divide = (num1, num2) => num1 / num2;

let x = 2; let y = 6; let z = 10;

const calcAsync = async() => { x = summarize(x, x);

console.log(await setTimeout(() => summarize(x, y))); console.log(await setTimeout(() => summarize(x, y))); console.log(await setTimeout(() => summarize(x, y))); console.log(await setTimeout(() => summarize(x, y)));

x = divide(x, z); }

calcAsync();

Нам же надо чтобы в x во втором then попало значение summarize(x, y).

Автором был предложен вариант, обернуть setTimeout в async ф-ию т.к. async нам возвращает Promise. Замечу, что писать в обёртке что-то типа return await setTimeout будет так же бесполезно как и если без обёртки. Давайте посмотрим, что будет в этом случае:

const asyncSum = async () => {return setTimeout(() => summarize(x, y), 1)}

const calcAsync = async() => { x = summarize(x, x);

x = await asyncSum();

x = divide(x, z); }

переписвая это на язык Promise-ов получим:

const asyncSum = () => {
  return Promise.resolve().then(function () {
    return setTimeout(() => summarize(x, y), 1);
  });
};

const calcAsync = () => { return Promise.resolve() .then(function () { x = summarize(x, x);

  return asyncSum();
})
.then(function (handleResolved) {
  x = handleResolved;
  x = divide(x, z);
});

};

Теперь можем спокойно подставить результат нашей ф-ии asyncSum туда, где он вызывается и получаем:

const calcAsync = () => {
  return Promise.resolve()
    .then(function () {
      x = summarize(x, x);
  return Promise.resolve().then(function () {
    return setTimeout(() => summarize(x, y), 1);
  });
})
.then(function (handleResolved) {
  x = handleResolved;
  x = divide(x, z);
});

};

И что мы видим? Мы опять отправляем в handleResolved наш timeoutID. Потому обёртки тут тоже не помогут отправить само значение summarize(x, y)

Теперь, когда разобрались, почему мы не можем ни await-тить ни оборачивать в async ф-ии наш setTimeout, посмотрим как нам помогает обёртка Promise-ом:

const calcAsync = async() => {
  x = summarize(x, x);

x = await new Promise((resolve) => { setTimeout(() => resolve(summarize(x, y)), 1); });

x = divide(x, z); }

опять переписываем на язык Promise-ов и получаем:

const calcAsync = () => {
  return Promise.resolve()
    .then(function () {
      x = summarize(x, x);
  return new Promise((resolve) => {
    setTimeout(() => resolve(summarize(x, y)), 1);
  });
})
.then(function (handleResolved) {
  x = handleResolved;
  x = divide(x, z);
});

};

Теперь видно, что мы явно в handleResolved передаём значение ф-ии summarize(x, y) в строке resolve(summarize(x, y)). Тут уже дело в том, что пока не будет вызыван/отработан resolve последующий then будет ждать, сколько бы это не заняло времени

P.S В документации сказано, что этот кусок кода:

async function foo() {
   return 1
}

похоже на:

function foo() {
   return Promise.resolve(1)
}

но я переписывал это как:

function foo() {
  return Promise.resolve().then(function () {
    return 1;
  });
}

в самих рассуждениях и примерах это ни на что не влияет. Вся логика будет точно такой же, если переписывать как показано в документации, но чтобы не переключаться постоянно между методами resolve и then, решил что, если всегда будет then, то будет меньше путаницы и это позволит лучше понять что происходит. Надеюсь смог добиться этого :)

EzioMercer
  • 6,178
  • 2
  • 9
  • 28
2

Можно воспользоваться тем, что await можно применить к thenable объекту, а именно объекту с методом then.

По аналогии с конструктором Promise метод then так же принимает два аргумента для resolve и reject.

const summarize = (num1, num2) => num1 + num2;
const divide = (num1, num2) => num1 / num2;

let x = 2; let y = 6; let z = 10;

const calcAsync = async() => { x = summarize(x, x); // (1шаг) 2 + 2 = 4 console.log('(1шаг)', x); x = await { then(r) { setTimeout(() => r(summarize(x, y)), 1) } }; // (2шаг) ??? должно получится 4 + 6 = 10 console.log('(2шаг)', x); x = divide(x, z); // (3шаг) ??? должно получится 10/10 = 1 console.log('(3шаг)', x); }

calcAsync();

Grundy
  • 81,538
  • Чёрт, зря расписывал всё это. Обидно... Но зато узнал что-то новое :) – EzioMercer Jun 28 '22 at 14:59
  • Неплохо, не знал что так можно О_о –  Jun 28 '22 at 21:00
  • @ДаЁпрстСколькоМожно примите пожалуйста ответ Grundy, чтобы я мог свой удалить :) – EzioMercer Jun 28 '22 at 21:34
  • @EzioMercer, зачем удалять-то? – Grundy Jun 28 '22 at 21:52
  • @Grundy ну ответ ошибочный. Зайдут сюда потом другие и увидят ответ с большим кол-ом голосов и галочкой и ошибочно будут думать что я прав. А я не хочу такое допускать :) Если я не прав, то я умею это признавать) – EzioMercer Jun 28 '22 at 21:57
  • @EzioMercer Ок, этот я приму, но ваш удалять не нужно, он тоже очень полезный и объясняет работу таймаутов и промисов. –  Jun 29 '22 at 00:23
  • @Siberian Хорошо, поправлю тогда свой ответ, вместо удаления раз уже двое настаивают на не удалении :) – EzioMercer Jun 29 '22 at 06:47
-1
await setTimeout(() => summarize(x, y), 1);
  1. setTimeout возвращает id таймера сразу, никакого толка от await тут нет.
  2. Да и вообще, результат вызова summarize не используется.
  3. 1ms - это странно. Скорее всего тут и нулю или отсутствующему аргументу неплохо.

Надо так:

x = await new Promise(resolve => setTimeout(resolve, 0, summarize(x, y)))

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

Нужно только с async/await без Promise, без then(), код должен быть максимально похож на синхронный.

Так не пойдёт: await работает именно с then.

Qwertiy
  • 123,725
  • А можно ли решить эту задачу вот как-то так: async function foo () { return 'Hello, world'; }

    (async function () { const text = await foo(); console.log(text); // Hello, world })(); ???

    –  Jun 27 '22 at 11:37
  • 1ms это для примера, можно поставить что угодно. –  Jun 27 '22 at 11:38
  • @ДаЁпрстСколькоМожно, а где в этом решении таймаут? – Qwertiy Jun 27 '22 at 12:25
  • таймаута нету, я просто хотел показать сам подход к решению, без new Promise –  Jun 27 '22 at 12:28