Как async await внутри .map работает за кулисами?

Это код, который я написал:

let aop = [
    'https://api.coindesk.com/v1/bpi/currentprice.json',
    'https://datausa.io/api/data?drilldowns=Nation&measures=Population',
    'https://api.publicapis.org/entries'
]; 
        
(async function (){
    Promise.all(aop.map(async url => {
        let response = await fetch(url);
        let json =  await response.json();
        return json;
    })).then(x => console.log(x))
} ());

(function functionThatTakes30SecondsToFinish (){
    // Some heavy lifting tasks
}())

Я получаю то, что хочу, но мне интересно, как это работает.

Promise.all нужен массив обещаний, поэтому я использовал .map для этого.

Теперь то, что меня смущает, - это первая итерация: для первого URL fetch вернет новое обещание, а затем встречается ключевое слово await. Поскольку вызов функции имеет более высокий приоритет, чем ключевое слово await, сначала возвращается обещание.

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

Но в этом случае будет возвращено наше первое обещание, и мы будем выброшены на один уровень за пределы глобального контекста выполнения, выполняя следующую функцию functionThatTakes30SecondsToFinish()

Очевидно, что ни одна функция не занимает так много времени, но, для примера, давайте притворимся.

После завершения этой трудоемкой функции мы уже работали только с одним URL-адресом? Это тоже только вернуло?

В любом случае, допустим, через 5 секунд мы получили наш первый ответ, а затем await возьмет следующий код:

let json =  await response.json();
return json;

... и поместите его в очередь микрозадач для последующего выполнения.

Если это работает только для одного URL, как тогда мы снова вернемся в map?

Или это второе, что, как я могу предположить, происходит:

map будет просматривать все URL-адреса, возвращать обещания, а затем вместо того, чтобы возвращать что-либо в свой внутренний скрытый массив, который в конечном итоге возвращается, он принимает весь этот код:

let json =  await response.json();
return json;

... и помещает его в очередь микрозадач для последующего выполнения.

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

Я настолько сбит с толку, что даже не могу спросить об этом должным образом. Может ли кто-нибудь прояснить это для меня?


person imheretolearn1    schedule 17.05.2021    source источник


Ответы (1)


Истина ближе ко второму вашему описанию.

Когда функция async выполняется, она возвращает, когда встречает await (после оценки выражения, которое следует за ней). Поведение .map заключается в том, что после возврата первого обратного вызова он выполняет обратный вызов для следующего элемента и т. Д. Каждый из них возвращается, когда сталкивается с await. Все это происходит синхронно.

Поэтому, когда .map выполнил все обратные вызовы, он возвращает массив с результатами. В этом случае результаты все обещают.

Затем выполняется Promise.all, что создает еще одно обещание. На нем выполняется метод then, но еще не его обратный вызов.

Затем выполняется тяжелый код подъема.

После того, как это завершится и стек вызовов станет пустым, будет отслеживаться очередь заданий обещания. Для fetch вызовов, получивших ответ, в этой очереди будет задание. Это могло бы быть даже 3 задания, если бы в этот период занятости приходили HTTP-ответы на все три запроса.

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

Поскольку число awaits (выполняется с .json() вызовами), функция вернется снова, на этот раз сделав стек вызовов пустым, что снова приведет к добавлению в очередь задания обещания (когда .json() выполнил свою работу).

Когда все эти await-ed обещания разрешены и весь код после его выполнения, обещания, переданные в Promise.all, будут разрешены. Это, в свою очередь, разрешит обещание, созданное Promise.all, и поэтому связанный обратный вызов then будет выполнен.

person trincot    schedule 17.05.2021
comment
В этом есть немного больше смысла, спасибо за ответ! Я думаю, что обратный вызов будет выполняться для каждого элемента, а затем, насколько мне известно, функцияThatTakes30SecondsToFinish () будет работать, потому что наши выборки все еще не разрешены. Скажем, через 30 секунд он разрешен, тогда остальной код помещается в очередь микрозадач для последующего выполнения, а затем .json () и другой код будут выполнены, и, наконец, он будет возвращен во внутренний массив .map, который будет в конечном итоге будет возвращен в Promise.all ()! Думаю, мне нужно больше тренироваться! :) - person imheretolearn1; 17.05.2021
comment
Я думаю, что обратный вызов будет выполняться для каждого элемента,: работает. тогда, я думаю, функцияThatTakes30SecondsToFinish () будет работать: но сначала будет выполняться Promise.all, а затем будет выполняться метод then, регистрируя обратный вызов для последующего выполнения. остальной код помещается в очередь микрозадач: нет, код не помещается в очередь. Это работа, своего рода вызов функции. , который будет возвращен во внутренний массив .map: нет, сопоставленный массив уже создан и имеет объекты обещаний. Единственное, что там происходит, - это то, что эти обещания переходят в состояние «решено». - person trincot; 17.05.2021
comment
Понятно, еще раз спасибо за вашу помощь! - person imheretolearn1; 17.05.2021