Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

fix($q): promises should still resolve outside of the $digest/$apply phase #3539

Closed

Conversation

jamestalmage
Copy link
Contributor

A number of issues have been raised (see #2431) because the current promise implementation has the unexpected behavior of not forwarding execution to the onResolve or onReject callbacks until the next $digest cycle, nor does it schedule a new cycle.

The currently recommended solution is to wrap all callbacks with $apply, but that is unnecessarily cumbersome (the user will almost always wants $apply called). It becomes increasingly problematic when dealing with longer promise chains, or using third party libraries that expect conventional promises as input.

Closes #2431

…phase

Add $rootScope.$apply() call upon resolve/reject if outside a digest phase

Closes angular#2431
@mary-poppins
Copy link

Thanks for the PR!

  • Contributor signed CLA now or in the past
    • If you just signed, leave a comment here with your real name
  • PR's commit messages follow the commit message format

If you need to make changes to your pull request, you can update the commit with git commit --amend.
Then, update the pull request with git push -f.

Thanks again for your help!

@jamestalmage
Copy link
Contributor Author

CLA is recently signed. Not sure what I did wrong on my commit message (I installed the hook)!

var deferreds = {};

var $q = qFactory(function(callback){
$rootScope.$evalAsync(callback);
},$exceptionHandler);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$timeout still needs the old nextTick implementation for ngAnimate/etc unit tests to pass.

I am not sure that my hack of $timeout is the appropriate one. Perhaps the promise API should be extended to include another function skipApply() that will suppress the $apply call upon promise resolution/rejection. i.e.:

var defer = $q.defer().skipApply();
// and/or
var promise = somePromiseReturningFunc();
promise.then(onResolve,onErr).skipApply();

even in the skipApply case I would think continuation of the promise chain should not wait for a $digest cycle, but simply prevent dirty checking of the scope.

@IgorMinar
Copy link
Contributor

Thanks for PR. We are aware of this issue, but I think your solution will cause too much breakage and timing issues.

The $timeout hack is just a manifestation of this.

Also with your change, you have to use $browser in your tests, which is one of the apis that is currently not even documented as public api and our goal is to completely get rid of it for many other reasons.

@jamestalmage
Copy link
Contributor Author

I am not so sure it would introduce new timing issues, except those that are desired.

Currently it has the same behavior as the old implementation unless the current evalAsync loop ends without a call to $apply. The following scenarios should all behave identically:

  1. Already in a $apply loop.
  2. $apply is called immediately after resolve or reject (i.e. per the documentation)
  3. $apply is called by some another callback on the evalAsync stack.

@jamestalmage
Copy link
Contributor Author

As for the $browser usage, that is trivial to replace when a more appropriate replacement is developed.

@ghost ghost assigned IgorMinar and btford Aug 21, 2013
IgorMinar added a commit to IgorMinar/angular.js that referenced this pull request Aug 22, 2013
This change causes a new $digest to be scheduled in the next tick if
a task was was sent to the $evalAsync queue from outside of a $digest
or an $apply.

While this mode of operation is not common for most of the user code,
this change means that $q promises that utilze $evalAsync queue to
guarantee asynchronicity of promise apis will now also resolve outside
of a $digest, which turned out to be a big pain point for some developers.

The implementation ensures that we don't do more work than needed and
that we coalese as much work as possible into a single $digest.

The use of $browser instead of setTimeout ensures that we can mock out
and control the scheduling of "auto-flush", which should in theory
allow all of the existing code and tests to work without negative
side-effects.

Closes angular#3539
Closes angular#2438
@IgorMinar
Copy link
Contributor

please take a look at the commit above (which I threw into PR #3699)

@IgorMinar
Copy link
Contributor

closing this one in favor of #3699

@IgorMinar IgorMinar closed this Aug 23, 2013
IgorMinar added a commit to IgorMinar/angular.js that referenced this pull request Aug 25, 2013
This change causes a new $digest to be scheduled in the next tick if
a task was was sent to the $evalAsync queue from outside of a $digest
or an $apply.

While this mode of operation is not common for most of the user code,
this change means that $q promises that utilze $evalAsync queue to
guarantee asynchronicity of promise apis will now also resolve outside
of a $digest, which turned out to be a big pain point for some developers.

The implementation ensures that we don't do more work than needed and
that we coalese as much work as possible into a single $digest.

The use of $browser instead of setTimeout ensures that we can mock out
and control the scheduling of "auto-flush", which should in theory
allow all of the existing code and tests to work without negative
side-effects.

Closes angular#3539
Closes angular#2438
IgorMinar added a commit that referenced this pull request Aug 26, 2013
This change causes a new $digest to be scheduled in the next tick if
a task was was sent to the $evalAsync queue from outside of a $digest
or an $apply.

While this mode of operation is not common for most of the user code,
this change means that $q promises that utilze $evalAsync queue to
guarantee asynchronicity of promise apis will now also resolve outside
of a $digest, which turned out to be a big pain point for some developers.

The implementation ensures that we don't do more work than needed and
that we coalese as much work as possible into a single $digest.

The use of $browser instead of setTimeout ensures that we can mock out
and control the scheduling of "auto-flush", which should in theory
allow all of the existing code and tests to work without negative
side-effects.

Closes #3539
Closes #2438
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Sep 25, 2013
This change causes a new $digest to be scheduled in the next tick if
a task was was sent to the $evalAsync queue from outside of a $digest
or an $apply.

While this mode of operation is not common for most of the user code,
this change means that $q promises that utilze $evalAsync queue to
guarantee asynchronicity of promise apis will now also resolve outside
of a $digest, which turned out to be a big pain point for some developers.

The implementation ensures that we don't do more work than needed and
that we coalese as much work as possible into a single $digest.

The use of $browser instead of setTimeout ensures that we can mock out
and control the scheduling of "auto-flush", which should in theory
allow all of the existing code and tests to work without negative
side-effects.

Closes angular#3539
Closes angular#2438
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
This change causes a new $digest to be scheduled in the next tick if
a task was was sent to the $evalAsync queue from outside of a $digest
or an $apply.

While this mode of operation is not common for most of the user code,
this change means that $q promises that utilze $evalAsync queue to
guarantee asynchronicity of promise apis will now also resolve outside
of a $digest, which turned out to be a big pain point for some developers.

The implementation ensures that we don't do more work than needed and
that we coalese as much work as possible into a single $digest.

The use of $browser instead of setTimeout ensures that we can mock out
and control the scheduling of "auto-flush", which should in theory
allow all of the existing code and tests to work without negative
side-effects.

Closes angular#3539
Closes angular#2438
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

No pending request to flush ! errors when moving from 1.1.3 to 1.1.4
4 participants