Асинхронное ожидание странного поведения с функциями

У меня есть следующий пример асинхронного кода:

// Functions

function getSomePromise() {
    let a = new Promise((resolve, reject) => {    
        setTimeout(function(){
            console.log("Inside promise...");
            resolve("Success!"); 
        }, 1000);
    });

    return a;
}

async function someWrapper(i) {
    console.log('A: '+ i);
    await getSomePromise();
    console.log('B: ' + i);    
}

И два теста:

async function test1() {
    for(let i=0; i<5; i++) {
        // body copy-pasted of someWrapper function:
        console.log('A: '+ i);
        await getSomePromise();
        console.log('B: ' + i);
    }    
}

async function test2() {
    for(let i=0; i<5; i++) {
        someWrapper(i);                
    }    
}

А вот результаты в хромированной консоли после запуска раздельно test1() и test2():

Test 1               |      Test 2
---------------------------------------------
A: 0                 |      A: 0
Inside promise...    |      A: 1
B: 0                 |      A: 2
A: 1                 |      A: 3
Inside promise...    |      A: 4
B: 1                 |      Inside promise...
A: 2                 |      B: 0
Inside promise...    |      Inside promise...
B: 2                 |      B: 1
A: 3                 |      Inside promise...
Inside promise...    |      B: 2
B: 3                 |      Inside promise...
A: 4                 |      B: 3
Inside promise...    |      Inside promise...
B: 4                 |      B: 4

Вопрос: Почему, когда мы используем функцию someWrapper() в for-loop (test2), мы получаем другой результат, чем когда мы копируем и вставляем тело этой функции непосредственно в for-loop (test1)?

(приведенный выше пример довольно абстрактен, однако «я обнаружил это поведение» при вызове запросов ajax (вместо console.log('A: '+ i); и console.log('B: '+ i);), последовательность которых очень важна в моем приложении (запрос A1 должен быть перед запросом B0...))


person Kamil Kiełczewski    schedule 07.02.2018    source источник
comment
Ваш первый тест использует async и await, которые запускают промисы по порядку. Второй тест не делает, поэтому каждый промис запускается, а потом заканчивает по порядку.   -  person Sidney    schedule 07.02.2018
comment
@Sidney, не могли бы вы рассказать подробнее - с моей точки зрения - я использую async/await внутри someWrapper(), а в Test2() я вызываю эту функцию синхронно (в цикле for).   -  person Kamil Kiełczewski    schedule 07.02.2018
comment
В цикле первого теста await заставляет цикл приостанавливаться в функции getSomePromise() до тех пор, пока обещание не будет разрешено. Во втором тесте нет await, поэтому JavaScript успешно продолжает цикл после запуска каждого промиса. Создание функции async на самом деле ничего не делает без использования await в функции.   -  person Sidney    schedule 07.02.2018
comment
@Sidney Единственное отличие состоит в том, что я перемещаю тело цикла (в тесте 1) в функцию (тест2). И, на мой взгляд, такое поведение противоречит здравому смыслу. Вы можете с этим согласиться?   -  person Kamil Kiełczewski    schedule 07.02.2018
comment
Другими словами: вызов функции async превращает необходимость ожидания значения в функцию Promise. Это означает, что вы можете сделать await someWrapper(i), чтобы получить тот же результат.   -  person JJWesterkamp    schedule 07.02.2018
comment
@Sidney - ты был первым - так что создай ответ (и подробнее уточни свое мнение об этом поведении: интуитивное или контринтуитивное), и я проверю его (на это я даю тебе 1 день).   -  person Kamil Kiełczewski    schedule 07.02.2018
comment
Следует помнить, что функции async немедленно возвращают промис, а с точки зрения вызывающей стороны await ничего не ждет.   -  person HMR    schedule 08.02.2018
comment
@HMR - хм ... Я не понимаю - в примере вопроса есть async function someWrapper(), но эта функция ничего не возвращает (у нее даже нет оператора return (!) ) - можете ли вы объяснить, что вы подразумеваете под async functions immediately return a promise ?   -  person Kamil Kiełczewski    schedule 08.02.2018
comment
someWrapper немедленно вернет обещание, которое разрешается в undefined. Ожидание ожидает только в функции someWrapper, но функция, вызывающая someWrapper, немедленно получит обещание, которое разрешается в undefined. Функции всегда что-то возвращают, если вы этого не сделаете в коде, он вернет undefined. Если это асинхронная функция без возврата, она вернет обещание, которое разрешается в undefined.   -  person HMR    schedule 08.02.2018
comment
@ Сидни - извините, но я вижу, что ваш ответ в комментарии не полностью объяснил проблему. HMR дает лучший ответ, поэтому я принимаю его.   -  person Kamil Kiełczewski    schedule 08.02.2018
comment
Я надеялся, что краткое объяснение поможет. У меня не было времени написать полный ответ. Я рад, что HMR смог объяснить!   -  person Sidney    schedule 08.02.2018


Ответы (2)


Глядя на комментарии

@HMR - хм... Я не понимаю - в примере вопроса есть асинхронная функция someWrapper(), но эта функция ничего не возвращает (у нее даже нет оператора return (!) ) - можете ли вы объяснить, что вы имеете в виду от async functions immediately return a promise? - Камиль Кельчевски

кажется, вы не понимаете асинхронное ожидание. Обычно я советую людям перестать ждать, пока вы не поймете обещания. Однако в следующем комментарии под вопросом я даю вам ответ:

someWrapper немедленно вернет обещание, которое разрешается в undefined. Ожидание только «ожидает» в функции someWrapper, но функция, вызывающая someWrapper, немедленно получит обещание, которое разрешается в undefined. Функции всегда что-то возвращают, если вы этого не сделаете в коде, он вернет undefined. Если это асинхронная функция без возврата, она вернет обещание, которое разрешается в undefined — HMR.

Await — это синтаксический сахар (красивее выглядящий код) для промисов и на самом деле ничего не ждет.

Возможно, следующий код проясняет ситуацию:

var test = async () => {
   await 22;//doesn't even matter if value is promise
   console.log("after wait");
}
var result = test();
console.log("outside test we don't wait for anything",result);

Если вы не понимаете, почему вывод этого кода:

вне теста мы ничего не ждем Promise {‹ pending >}

после ожидания

Тогда я бы посоветовал вам использовать только обещания, пока вы этого не сделаете.

person HMR    schedule 08.02.2018
comment
Я редактирую ваш ответ и добавляю наши комментарии-вопросы, которые дают ключевую подсказку - что async function не является нормальной функцией, но всегда возвращает обещание (это разница между асинхронной и нормальной (не асинхронной) функцией - из чего я не понял) - это был ключевым моментом для меня, чтобы понять, почему пример вопроса ведет себя так - person Kamil Kiełczewski; 08.02.2018
comment
Есть еще одна вещь: вы говорите, что Await is syntax sugar (nicer looking code) for promises and doesn't actually wait for anything. - 'все', кроме обещания закончить (решено или отклонено) - ? - person Kamil Kiełczewski; 08.02.2018
comment
У меня также есть вопрос к вашему фрагменту - если я удалю строку await 22, то первая строка в консоли будет after wait (последовательность переключателя печати строки консоли). И я не понимаю - почему в этом случае обещание от var result = test(); выполняется немедленно (и что-то выводит на консоль)? - person Kamil Kiełczewski; 08.02.2018
comment
@KamilKiełczewski Он не ждет ничего за пределами асинхронной функции. Когда в функции нет ожидания, код после ожидания не ставится в очередь, поэтому порядок журналов другой. Я бы посоветовал снова использовать синтаксис обещания, а не асинхронный. Только что обновил другой ответ (в стадии обновления), который, вероятно, даст мне некоторый ответ от людей OO, но я думаю, что объект обещания идеальный тип данных, а также асинхронность и ожидание обычно нужны только потому, что вы пишете функцию, которая делает слишком много. - person HMR; 08.02.2018
comment
Возможно, это тоже может помочь. - person HMR; 08.02.2018

тест2:

ваш test2 не является асинхронным, пока вы не сделаете его асинхронным. Вы написали синхронный код внутри test2. Это console.log. Только асинхронный код внутри test2 является вызовом promise. Давайте разберем его.

async function test2() {
    for(let i=0; i<5; i++) {
        someWrapper(i);                
    }    
}

приведенный выше код запускает someWrapper() 5 раз подряд, поэтому он записывает первый код sync, который console.log('A'+i) 5 раз подряд в строке в консоли.

затем каждый someWrapper() ожидает возврата async обещания параллельно. После того, как каждое обещание разрешено, он печатает «Внутреннее обещание». пока обещание не будет разрешено, выполнение останавливается и невозможно перейти к следующему шагу

затем, после разрешения промиса, он выводит второй код sync, который в консоли console.log('B'+i)

тест1:

test1 будет вести себя иначе, чем test2. Давайте разберем его.

async function test1() {
    for(let i=0; i<5; i++) {
        // body copy-pasted of someWrapper function:
        console.log('A: '+ i);
        await getSomePromise();
        console.log('B: ' + i);
    }    
}

главное отличие в том, что вы awaiting inside for loop. Таким образом, это будет буквально pause loop, чего не было в случае с test1.

поэтому для каждой итерации он будет печатать console.log('A'+i)

затем приостановите итерацию для await getSomePromise()

когда обещание вернется, будет напечатано «Внутреннее обещание»

затем напечатайте console.log('B'+i)

затем продолжите следующую итерацию.

person AL-zami    schedule 07.02.2018
comment
@AL-zami - на ваш взгляд, такое поведение JS интуитивно понятно или противоречит здравому смыслу? (потому что, с моей точки зрения, во время рефакторинга я только перемещаю тело цикла for в отдельную функцию, и я был удивлен, что это движение меняет поведение...) - person Kamil Kiełczewski; 07.02.2018
comment
@KamilKiełczewski на самом деле javascript является однопоточным. внутри асинхронного кода код синхронизации будет выполняться синхронно. async не делает код синхронизации асинхронным. - person AL-zami; 07.02.2018
comment
одно, а именно, это не противоречит здравому смыслу. и обещание, и асинхронное ожидание введены для устранения беспорядка в цепочке обратного вызова. Они помогают писать асинхронный код синхронно. - person AL-zami; 07.02.2018
comment
@ AL-zami Я думаю, что это нелогично, поскольку ОП и многие другие вопросы, которые я видел, думают, что await на самом деле ожидает вместо функции async, немедленно возвращающей обещание. Если бы люди научились работать с промисами до использования асинхронного синтаксиса, здесь было бы намного меньше вопросов по этому поводу. - person HMR; 08.02.2018
comment
@HMR да! Я также столкнулся с трудностями, понимая это в первую очередь. Но когда картина проясняется, она уже не кажется такой нелогичной. Я согласен с вами, кривая обучения должна быть такой: Promise › Generators › async-await - person AL-zami; 08.02.2018