Обещания angular вводят в заблуждение тем, что они не всегда вызываются асинхронно

Недавно я отлаживал некоторые вещи, связанные с обещаниями в angular.js, и заметил, что angular помещает разрешения обещаний в свой цикл evalAsync, как показано в эта диаграмма. У меня создалось впечатление, что обратные вызовы обещаний всегда выполняются асинхронно (как новое событие в очереди событий). Однако с механизмом angular возможно, что если какое-либо из обещаний будет разрешено во время цикла дайджеста и angular начнет другую итерацию по дайджесту, обратный вызов для обещания будет вызываться в том же стеке выполнения, поскольку очередь evalAsync всегда проверяется первой:

do { // "while dirty" loop
  dirty = false;
  current = target;

  while (asyncQueue.length) {
    try {
      asyncTask = asyncQueue.shift();
      asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
    } catch (e) {
      $exceptionHandler(e);
    }
    lastDirtyWatch = null;
  }

  traverseScopesLoop:
  do { // "traverse the scopes" loop
  ...
  } while ((current = next));
...  
} while (dirty || asyncQueue.length);

Разве не вводит в заблуждение?


person Max Koretskyi    schedule 18.04.2015    source источник
comment
Вот как должны работать обещания. В любом случае функция не может точно сказать, находится ли она в том или ином конкретном цикле событий.   -  person Pointy    schedule 18.04.2015
comment
Спасибо, я этого не знал. У меня создалось впечатление, что обещания всегда разрешаются в новом цикле событий, поскольку разрешение в том же цикле событий на самом деле не асинхронно, верно?   -  person Max Koretskyi    schedule 18.04.2015
comment
Это правда, но поскольку ваш обратный вызов не знает, что произойдет, он должен быть написан так, как если бы он был вызван асинхронно.   -  person Pointy    schedule 18.04.2015
comment
На самом деле, прочитав немного, я думаю, что мои комментарии неверны; спецификация обещания более тонкая и надежная, чем я думал, и я недостаточно знаю о том, что происходит в Angular, чтобы судить об этом.   -  person Pointy    schedule 18.04.2015


Ответы (1)


Не уверен, назвал бы я это заблуждением или нет. Факт:

  • Обратные вызовы Promise всегда выполняются асинхронно. Они никогда не вызываются до того, как .then(), которому они были переданы для возврата.
    В большей степени они даже никогда не вызываются из пользовательского кода - в стеке есть "только код платформы", как предписано Promises / A + spec.
  • Не гарантируется, что каждый асинхронный обратный вызов выполняется в свою очередь цикла событий. Нет требования, чтобы два обратных вызова не могли совместно использовать один и тот же цикл цикла событий. В конце концов, они все равно не могут этого различить.

В вашем случае Angular квалифицируется как код платформы, который использует свой собственный «цикл событий» - цикл дайджеста.

person Bergi    schedule 18.04.2015
comment
Итак, если я передаю функцию методу .then() на уже выполненном экземпляре обещания, реализация должна будет сделать что-то вроде расписания немедленного тайм-аута для его вызова? Или подождать, пока какой-нибудь интервальный таймер его найдет? Это странно, но в спецификации определенно сказано именно так. - person Pointy; 18.04.2015
comment
@Pointy: Именно так. Спецификация гарантирует асинхронность. Никаких странных угловых случаев, когда ваш обратный вызов иногда происходит до возврата .then(…), только потому, что обещание неожиданно уже выполнено. - person Bergi; 18.04.2015
comment
Ладно, верно. setImmediate() Обсуждение ошибки gecko бессвязно, но информативно. Эта проблема кажется интересной и обманчиво сложной, если не считать основных поведенческих гарантий (которые несложны). - person Pointy; 18.04.2015
comment
@Bergi, спасибо, так что, насколько я понимаю, обратный вызов здесь заключается в том, что обратные вызовы никогда не вызываются до .then (), в который они были переданы, возвращает, однако обратный вызов может быть выполняется в том же цикле событий, что и функция, вызывающая метод .then() некоторым кодом платформы, когда существует стек кода пользователя, верно? - person Max Koretskyi; 18.04.2015
comment
@Maximus: Да, он может быть выполнен в том же цикле событий, что и вызов then, но не тогда, когда в стеке еще находится некоторый пользовательский код. Примером (надуманным) будет p.then(function(){ p.then(/*… later */); …} /* can be *now* */) - внутренний обратный вызов может быть вызван сразу после возврата внешнего. - person Bergi; 18.04.2015
comment
@ Берги, понятно, спасибо. Но я не понял твоего примера. Не могли бы вы опубликовать его как часть своего ответа в удобочитаемом формате. Я отформатировал его, и кажется, что вы поместили /* can be *now* */ часть не в то место или я что-то упустил. - person Max Koretskyi; 18.04.2015
comment
@Maximus: Нет, я специально ставил после первой функции. Но, вероятно, вставлять это в код было плохой идеей. Я имел в виду, что в ситуации promise.then(function a(){ … promise.then(b); … }) оба обратных вызова могут выполняться в одном и том же ходе цикла событий: a(); b();, даже если b был зарегистрирован в обещании в a(). Но из области действия функции a это не имеет значения, b всегда выполняется позже, после кода пользователя . - person Bergi; 18.04.2015
comment
@Bergi, вы говорите, что оба обратных вызова могут выполняться на одном и том же этапе цикла событий. Вы имеете в виду, что при некоторых обстоятельствах они не будут? - person Roamer-1888; 18.04.2015
comment
@ Roamer-1888: Я имел в виду, что это зависит от реализации. Реализация может отложить такие вызовы до следующего хода или выполнить их как можно скорее. Angular, кажется, делает последнее. - person Bergi; 18.04.2015
comment
@Bergi, спасибо за объяснение. теперь имеет смысл. - person Roamer-1888; 18.04.2015