ES2017 - Асинхронность против доходности

Меня смущает текущее обсуждение добавления асинхронных функций и ключевого слова await в следующий EcmaScript.

Я не понимаю, почему необходимо ключевое слово async перед ключевым словом function.

С моей точки зрения ключевого слова await для ожидания результата генератора или обещания done, return функции должно быть достаточно.

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

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

Причина, по которой я спрашиваю, - это это хорошее объяснение, откуда взялся следующий пример:

async function setupNewUser(name) {  
  var invitations,
      newUser = await createUser(name),
      friends = await getFacebookFriends(name);

  if (friends) {
    invitations = await inviteFacebookFriends(friends);
  }

  // some more logic
}

Это также может быть выполнено как обычная функция, если выполнение функции будет ждать завершения функции отверстия, пока не будут выполнены все ожидания.

function setupNewUser(name) {  
  var invitations,
      newUser = await createUser(name),
      friends = await getFacebookFriends(name);

  if (friends) {
    invitations = await inviteFacebookFriends(friends);
  }

  // return because createUser() and getFacebookFriends() and maybe inviteFacebookFriends() finished their awaited result.

}

На мой взгляд, выполнение всей функции задерживается до следующего тика (ожидания выполнения). Отличие от функции-генератора заключается в том, что next () запускает и изменяет значение объекта и поле done. Вместо этого функция просто вернет результат, когда это будет выполнено, а триггер - это внутренний триггер функции, такой как цикл while.


person Danny    schedule 17.07.2015    source источник
comment
Если бы вы заблокировали эту функцию, это была бы не только эта функция, но и функция, вызывающая ее, и так далее по стеку вызовов, блокирование, что является серьезным отклонением от того, как JS работает в настоящее время. Функции, помеченные как асинхронные, возвращают обещания для значений, чтобы избежать этого, так как вызывающий будет продолжать выполнение и в конечном итоге получит результат из результата обещания.   -  person loganfsmyth    schedule 17.07.2015
comment
Не уверены, связано ли ваше замешательство также с различием между async и функциями генератора?   -  person Bergi    schedule 18.07.2015
comment
@Bergi: Да, я думаю, я не имел представления, что async создаст в дереве объектов. Генераторы я хорошо знаю. Асинхронность меня смущала, потому что, похоже, мне кажется, что нет причин что-то менять (см. Цикл while). Но спасибо за ваш ответ ниже - я тоже прокомментировал ...   -  person Danny    schedule 18.07.2015
comment
@loganfsmyth: Да, в этом есть смысл. Цикл while заблокировал бы все (из-за LIFO) вне цикла событий. Асинхронность будет организована в рамках цикла событий (что такое FIFO)?   -  person Danny    schedule 18.07.2015
comment
@ Дэнни: Ты знаешь, как работают обещания? В противном случае вам, вероятно, следует сначала узнать о них, прежде чем пытаться понять синтаксис _1 _-_ 2_.   -  person Bergi    schedule 18.07.2015
comment
@Bergi: да, я знаю - я полностью запутался, пытаясь понять внутренние вещи (что происходит под капотом). Первоначальный вопрос был о необходимости асинхронного слова. Теперь я знаю, что речь идет о том, какой тип объекта (включая его методы) создается.   -  person Danny    schedule 18.07.2015


Ответы (4)


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

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

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

Это правда, что ключевое слово не является строго необходимым, и тип функции может определяться тем, появляются ли в ее теле соответствующие ключевые слова (_5 _ / _ 6_), но это привело бы к менее удобному в сопровождении коду:

  • менее понятен, потому что нужно сканировать все тело, чтобы определить вид
  • более подвержен ошибкам, потому что легко сломать функцию, добавив / удалив эти ключевые слова, не получая синтаксической ошибки

обычная функция, выполнение которой будет ждать завершения тела отверстия, пока не будут выполнены все ожидания

Похоже, вам нужна функция блокировки, что является очень плохой идеей в параллельной настройке. .

person Bergi    schedule 17.07.2015
comment
спасибо - наконец причина такова: он ведет созданный объект. Объект-генератор будет иметь значение и выполненный, функция, простая объект-функция, и асинхронная функция создадут готовое, без значения, кроме массива ожидания. Движок JS будет работать, и обратите внимание на этот тип флагов при управлении потоком? - person Danny; 18.07.2015
comment
Гм, нет. Объект-генератор будет иметь методы next, return и throw (которые возвращают пары _4 _ / _ 5_), обещание будет иметь метод then (который выполняет обратный вызов с одним из двух значений). Не уверен, что вы имеете в виду под флагами или массивом ожидания. Основное различие заключается в том, что код генератора возобновляется с yield, когда он вызывается извне (если вообще, и довольно произвольно), а асинхронный код возобновляется с await, когда выполняется текущее ожидаемое обещание. - person Bergi; 18.07.2015
comment
Есть ли лучший способ отслеживать состояния, чем console.log (объект)? Я попытался понять это, создав несколько последовательностей, но указанный объект пуст. В случае генераторов я получаю {value: "foo", done: false}, если console.log(generator.next());, но мне нравится видеть генератор дырок, чтобы понять, что происходит под капотом. - person Danny; 18.07.2015
comment
Вы можете сделать console.log(generator), а также поместить журналы в функцию генератора между yields. Однако я не думаю, что есть способ проверить текущее состояние генератора. - person Bergi; 18.07.2015
comment
Я пробовал и то, и другое раньше. var gen =function* generator(){ console.log(gen);yield ...}; но, как вы сказали, это не помогает. - person Danny; 18.07.2015
comment
Не путайте функцию генератора с самим экземпляром генератора. Я думаю, что davidwalsh.name/es6-generators достаточно хорошо это объясняет. - person Bergi; 18.07.2015

Помечая функцию как async, вы говорите JS всегда возвращать обещание.

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

Например, эта функция ...

async function test() {
  return 'hello world';
}

... возвращает обещание. Таким образом, вы можете выполнить его как единое целое, .then() и все такое.

test().then(message => {
  // message = 'hello world'
});

So...

async function test() {
  const user = await getUser();
  const report = await user.getReport();
  report.read = true
  return report;
}

Примерно аналогично ...

function test() {
  return getUser().then(function (user) {
    return user.getReport().then(function (report) {
      report.read = true;
      return report;
    });
  });
}

В обоих случаях обратный вызов, переданный в test().then(), получит report в качестве первого параметра.

Генераторы (т. Е. Маркировка функции * и использование ключевого слова yield) - это совсем другое понятие. Они не используют обещания. Они эффективно позволяют вам «прыгать» между различными частями вашего кода, выдавая результат изнутри функции, а затем возвращаясь к этой точке и возобновляя выполнение следующего блока yield.

Хотя они кажутся чем-то похожими (т.е. «останавливают» выполнение до тех пор, пока что-то не произойдет где-то еще), async/await только дает вам эту иллюзию, потому что нарушает внутренний порядок выполнения обещаний. Это не на самом деле ожидание - это просто перетасовка, когда происходят обратные вызовы.

Генераторы, напротив, реализованы по-другому, так что генератор может поддерживать состояние и повторяться. Опять же, ничего общего с обещаниями.

Граница еще больше размыта, потому что на текущий момент поддержка async / await пугает; Chakracore изначально поддерживает его, и Скоро это будет у V8. Тем временем транспиляторы, такие как Babel, позволяют писать async/await и преобразовывать код в генераторы . Ошибочно заключать, что генераторы и async / await, следовательно, одинаковы; они не ... просто так получилось, что вы можете ублюдать, как yield работает вместе с обещаниями, чтобы получить аналогичный результат.

Обновление: ноябрь 2017 г.

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

person Lee Benson    schedule 08.09.2016
comment
очень старательный ответ - person prosti; 02.12.2016
comment
Ты действительно прояснил это для меня. co использует генераторы, и теперь я понимаю, _2 _ / _ 3_ были просто недоступны, когда это было сделано (если я ошибся). - person Camilo Martin; 14.08.2017
comment
Я читал это сообщение в блоге: blog.jscrambler.com/introduction-to-koajs (о библиотеке koa.js), и мне было интересно, почему в примерах широко используются генераторы и не используются async / await (что сбило меня с толку относительно природы обоих - я новичок в них). Ваш ответ, кажется, предполагает, что синтаксис, используемый в сообщении, следует считать устаревшим (если поддерживается async / await). - person Nicolas Le Thierry d'Ennequin; 07.11.2017
comment
Да, это - Koa v1. Коа 2 использует обещания. - person Lee Benson; 07.11.2017

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

Причина в том, что это был действующий JS до ES7.

function await(x) {
  return 'awaiting ' + x
}

function foo() {
  return(await(42))
}

Согласно вашей логике, foo() вернет Promise{42} или "awaiting 42"? (возврат обещания нарушит обратную совместимость)

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

Интересный факт: исходная спецификация предлагала более легкий function^ foo() {} синтаксис async.

person phaux    schedule 19.01.2017
comment
await(42) мог просто быть недопустимым ES7. Мы могли быть вынуждены использовать его без скобок, как в await 42, что в любом случае делают на практике все. Это настоящий облом, когда вы вынуждены отмечать каждую функцию знаком async, когда вы попадаете в ловушку, а еще одну - не иметь возможности использовать await в обычных функциях, возвращающих обещания. Это могло быть намного лучше ... - person DanielM; 06.10.2018
comment
Возможно, await необходимо принять синтаксис скобок, чтобы разрешить самоисполняющиеся функции await (function(){return promisify();})(); - person Kernel James; 07.04.2021

Причина ключевого слова async впереди проста, поэтому вы знаете, что возвращаемое значение будет преобразовано в обещание. Если нет ключевого слова, как бы переводчик узнал об этом. Я думаю, что это было впервые введено в C #, и EcmaScript берет добычу из TypeScript. TypeScript и C # разработаны Андерсом Хейлсбергом и похожи друг на друга. скажем, у вас есть функция (это просто для асинхронной работы)

 function timeoutPromise() {  
     return (new Promise(function(resolve, reject) {
         var random = Math.random()*1000;
         setTimeout(
             function() {
                 resolve(random);
             }, random);
     }));
 }

эта функция заставит нас ждать случайное время и вернуть объект Promise (если вы используете jQuery, обещание похоже на Deferred). Чтобы использовать эту функцию сегодня, вы должны написать что-то вроде этого

function test(){
    timeoutPromise().then(function(waited){
        console.log('I waited' + waited);
    });
}

И это нормально. Теперь давайте попробуем вернуть сообщение журнала

function test(){
    return timeoutPromise().then(function(waited){
        var message = 'I waited' + waited;
        console.log(message);
        return message; //this is where jQuery Deferred is different then a Promise and better in my opinion
    });
}

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

Теперь с async это будет выглядеть так

  async function test(){
      var message = 'I waited' +  (await timeoutPromise());
      console.log(message);
      return message;
  }

Код короткий и встроенный. Если вы написали много .then () или. done () вы знаете, насколько нечитаемым может стать код.

Теперь почему ключевое слово async перед функцией. Это означает, что ваше возвращаемое значение - это не то, что возвращается. Теоретически вы могли бы написать это (это можно сделать на C #, я не знаю, разрешит ли js, поскольку это еще не сделано).

 async function test(wait){
     if(wait == true){
         return await timeoutPromise();
     }
     return 5;                
 }

вы видите, вы возвращаете число, но фактический возврат будет Promise, который вам не нужно использовать return new Promise(function(resolve, reject) { resolve(5);}; Поскольку вы не можете ожидать числа, только Promise await test(false) вызовет исключение, а await test(true) не будет, если вы не укажете async спереди.

person Filip Cordas    schedule 09.06.2016
comment
В вашей первой test функции отсутствует символ return? И я не понимаю, чем отложенные jQuery лучше, чем настоящие обещания, они работают точно так же для возвращаемых значений. - person Bergi; 09.06.2016
comment
timeoutPromise возвращает (новое обещание). И я не понимаю, чем лучше отложенные jQuery - person Filip Cordas; 10.06.2016
comment
Кажется, что в вашей первой тестовой функции отсутствует возврат? timeoutPromise возвращает (новое обещание). И я не понимаю, чем лучше отложенные jQuery? у вас есть больше функций, таких как всегда, готово, прогресс. ссылка - person Filip Cordas; 10.06.2016
comment
timeoutPromise что-то возвращает, да, но первая реализация test - нет. Что касается отложенных, jQuery ужасен и не соответствует стандартам, always - это довольно тривиально, done бесполезно, а progress не рекомендуется во всех основных библиотеках по уважительной причине. - person Bergi; 10.06.2016
comment
@Bergi jQuery 3.0 исправил их обещания и заставил их соответствовать спецификации Promises / A +. - person Flimm; 23.08.2016
comment
@Flimm Хорошо для тех, кто уже использует jQuery 3… но их API все еще хуже, чем даже простые реализации ES6. - person Bergi; 23.08.2016