Зачем мне ждать асинхронную функцию, если она не возвращает Promise? - PullRequest
1 голос
/ 05 июля 2019

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

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

function main() {
  const data = load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

Это вывод, который я получаю:

Loaded data: {}
Data inside the function: [10,20,30]

Но если я изменю код на это:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

<b>async</b> function main() {
  const data = <b>await</b> load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

Я получу это:

Data inside the function: [10,20,30]
Loaded data: [10,20,30]

Я в замешательстве, потому что на основе документации , await следует приостановить выполнение, пока обещание не будет выполнено. В этом случае первый пример должен возвращать data в виде массива. Но, как вы видите, он возвращает Promise, и я понятия не имею, почему!?

В то же время в документации есть эта часть, о которой я не понимаю:

Ожидание может разделить поток выполнения, позволяя абоненту ожидающего функция возобновления выполнения до отложенного продолжения функция ожидания. После того как ожидание откладывает продолжение его функция, если это первое ожидание, выполняемое функцией, немедленное выполнение также продолжается, возвращаясь к функции Вызывающий ожидающий обещание для завершения функции ожидания и возобновить выполнение этого вызывающего абонента.

Мне кажется, await работает, только если все функций в вашем коде async, что нелепо, так как, если я использую функцию из другого модуля, как я должен знать, если это async или нет !? Или, может быть, мне следует проявить осторожность и всегда вызывать все функции с await независимо от того, являются они async или нет !!!

[UPDATE]

Спасибо всем, кто принял участие и дал мне понимание. Но я все еще не понимаю, как мне использовать await и async. Должен ли я всегда вызывать все свои функции с await?

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

Ответы [ 3 ]

7 голосов
/ 05 июля 2019

Все async функции возвращают обещание.Все они.

Это обещание в конечном итоге разрешится с любым значением, которое вы вернете из асинхронной функции.

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

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

К вашему сведению, в вашей load() функции много лишних вещей.Вы можете изменить его следующим образом:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

на это:

function load() {
    return new Promise(resolve => {
        setTimeout(() => resolve([1, 2, 3]), 10);
    }).then(data => data.map(i => i * 10));
}

Затем используйте его так:

load().then(result => {
    console.log(result);
});

Или я предпочитаю инкапсулироватьручное создание обещания в их собственной функции, например:

function delay(t, v) {
    return new Promise(resolve => {
        setTimeout(resolve.bind(null, v), t);
    });
}

function load() {
    return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}

И, оказывается, эта небольшая функция delay() обычно полезна во многих местах, где вы хотите отложить цепочку обещаний.


Спасибо всем, кто принял участие и дал мне понимание.Но я все еще не понимаю, как использовать await и async.

Во-первых, большую часть времени вы отмечаете только функцию async, если вам нужно использовать await внутри функции.

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

Вот несколько примеров веских причин для использованияasync/await:

Последовательность нескольких асинхронных операций

Представьте, что у вас есть getFromDatabase(), getTheUrl() и getTheContent(), которые все асинхронны.В случае сбоя вы можете просто отклонить возвращенное обещание с первой ошибкой.

Вот как это выглядит без асинхронного / await:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}

Вот как это выглядит с async/await:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

В обоих случаях функция возвращает обещание, которое разрешается с помощью finalValue, поэтому вызывающая сторона использует эти две реализации одинаково:

run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

Но вы заметите,что реализация async/await имеет больше сериализованного синхронного вида и больше похожа на не асинхронный код.Многие считают, что это легче писать, легче читать и легче поддерживать.Чем больше обработки вы выполняете между этапами, включая переход, тем больше преимуществ дает версия async/await.

Автоматический сбор отклоненных обещаний и синхронных исключений

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

Само собой разумеется, что если вы вручную вернете обещание из функции async и это обещание будет отклонено, то обещание, возвращенное из функции async, будет отклонено.

Но также,если вы используете await и любое обещание, которое вы ожидаете, отклоняется, и у вас нет .catch() на обещании и у вас нет try/catch вокруг него, то обещание, которое возвращает функция, будет автоматически отклонено.Итак, вернемся к нашему предыдущему примеру этого:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

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

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

В обычной функции, которая возвращает обещание, такое как мы имели ранее:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}

Если getFromDatabase() выдает синхронное исключение (возможно, вызванное тем, что someArg недопустимо), то эта общая функция run() будет генерировать синхронно.Это означает, что для того, чтобы вызывающий мог перехватить все возможные ошибки из run(), он должен оба окружить его try/catch, чтобы перехватить синхронные исключения, и использовать .catch(), чтобы перехватить отклоненное обещание:

try {
    run(someArg).then(finalValue => {
        console.log(finalValue);
    }).catch(err => {
        console.log(err);
    });
} catch(e) {
    console.log(err);
}

Это грязно и немного повторяется.Но когда run() объявлено async, оно НИКОГДА не будет генерировать синхронно, потому что любое синхронное исключение автоматически преобразуется в отклоненное обещание, поэтому вы можете быть уверены, что фиксируете все возможные ошибки, когда оно написано таким образом:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

// will catch all possible errors from run()
run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

Должен ли я всегда вызывать все свои функции с ожиданием?

Во-первых, вы бы когда-либо использовали await только с функцией, которая возвращает обещание как awaitне дает никакой пользы, если функция не возвращает обещание (просто добавляя в беспорядок ваш код, если не нужно).

Во-вторых, используете ли вы await или нет, зависит от контекста обеих вызывающих функций(поскольку вы ДОЛЖНЫ быть в функции async, чтобы использовать await и в потоке логики, и будет ли полезно использовать await или нет.

Места, где использовать бессмысленноawait

async function getKey(someArg) {
    let key = await getFromDatabase(someArg);
    return key;
}

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

async function getKey(someArg) {
    return getFromDatabase(someArg);
}

И, если вы знаете, что getFromDatabase() никогда не генерирует синхронно, вы даже можете удалить async из объявления:

function getKey(someArg) {
    return getFromDatabase(someArg);
}

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

Это слишком общий вопрос, на который сложно ответить в общем случае.Вот некоторые мысли в этом общем направлении:

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

  2. Любойфункция B(), которая вызывает вашу асинхронную функцию A(), которая также пытается вернуть результат на основе того, что она получает от A(), теперь также асинхронна и также должна сообщать свой результат обратно, используя асинхронный механизм.Это верно для функции C(), которая вызывает B() и должна сообщать вызывающему ее результат.Итак, вы можете сказать, что асинхронное поведение заразительно.Пока вы не достигнете некоторой точки в цепочке вызовов, где вам больше не нужно сообщать результат, все должно использовать асинхронные механизмы для передачи результата, ошибки и завершения.

  3. Нет особой необходимости отмечать функцию async, если вам не требуется одно из преимуществ функции async, например, возможность использовать await внутри этой функции или автоматическая обработка ошибок, которую она обеспечивает.,Вы можете написать функции, которые возвращают обещания, просто без использования async в объявлении функции.Итак, «НЕТ», я не возвращаюсь по цепочке вызовов, делая все async.Я делаю функцию асинхронной только в том случае, если для этого есть особая причина.Обычно причина в том, что я хочу использовать await внутри функции, но есть также автоматический перехват синхронных исключений, которые превращаются в отклонения обещаний, которые я описал ранее.Обычно это не требуется для кода с хорошим поведением, но иногда это полезно для кода с плохим поведением или кода с неопределенным поведением.

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

Или, может быть, я должен просто привыкнуть вызывать все мои функции с ожиданием, независимо от того, являются они асинхронными или нет?

Абсолютно НЕТ!Во-первых, последнее, что вы хотите сделать, - это взять совершенно синхронный код и излишне сделать его асинхронным или даже сделать его асинхронным.Асинхронный код (даже с async и await) сложнее писать, отлаживать, понимать и поддерживать, чем синхронный код, поэтому вы никогда не захотите без необходимости превращать синхронный код в асинхронный код, добавляя в него async/await:

Например, вы никогда бы не сделали это:

async function random(min, max) {
    let r = await Math.random();
    return Math.floor((r * (max - min)) + min);
}

Во-первых, это совершенно синхронная операция, которая может быть закодирована следующим образом:

function random(min, max) {
    let r = Math.random();
    return Math.floor((r * (max - min)) + min);
}

Второй выключен,эта первая реализация async усложнила использование функции, поскольку теперь она имеет асинхронный результат:

 random(1,10).then(r => {
     console.log(r);
 });

Вместо простого синхронного использования:

 console.log(random(1,10));
2 голосов
/ 05 июля 2019

async / await - это просто синтаксический сахар, а это значит, что они не привносят в язык никакой новой функциональности, а являются просто полезными обертками для обещаний.

Если функция помечена как async, она всегда возвращает обещание:

> async function f() { return 42; }
undefined
> f()
Promise { 42 }

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

Чтобы ответить на ваш вопрос: если вы используете библиотечную функцию, вы обычно знаете, возвращает ли она обещание или нет (и если он помечен как async, это точно так и есть). Поэтому обязательно наберите await или используйте .then с возвращенным обещанием.

1 голос
/ 05 июля 2019

Поскольку первая функция является асинхронной, поэтому она работает, пока выполняется остальная часть функции main, что бесполезно, когда результат регистрируется в следующей строке.Вам нужно дождаться завершения выполнения функции, прежде чем вы сможете использовать результат - так что либо используйте async / await, как в вашем примере:

async function main() {
  const data = await load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

, либо используйте .then:

function main() {
  load().then(data => {
    console.log(`Loaded data: ${JSON.stringify(data)}`);
  });
}

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

...