Resolving promises without a digest cycle - javascript

AngularJS promises seem to be tied to a digest cycle, as in, the success/error callbacks are not called until a digest cycle is run. This means that anything that uses promises, such as $http or manually created promises, also need to trigger a digest cycle in order to get the callbacks to run.
Is it possible to use promises in Angular, without the digest cycle being run at all? I realise you can use $applyAsync, which schedules the digest cycle for a bit later, but I'm looking to not run the digest cycle at all, and still have the then callbacks run.
Essentially I'm trying to work out how to squeeze as much performance as possible from an app that would use a fair bit of asynchronous behaviour that would need promises resolved but not necessarily the digest cycle run.

No, it is currently not possible. Whenever a then handler runs it schedules the callback via $evalAsync which schedules a digest if one is not already scheduled.
The exception to this is $timeout that accepts an extra argument to not run a digest. On the other hand - multiple promises that resolve in the same turn run on the same digest.
Your options are:
- Use XMLHttpRequqest directly, seriously - it's not very hard. The biggest downside to this is that it will not respect interceptors and other $http hooks (like the mock backend).
- Decorate $q to not schedule via $evalAsync or add a .thenNoDigest method to the promise prototype that schedules via setTimeout.
- Use a userland promise library instead of $q for $http or over XHR.

Related

How to cause fetch calls to queue up once I've reached max number of concurrent calls?

I have code that will make a number of concurrent calls to a piece of software that provides a rest interface to query our system. We recently discovered that a sufficiently high number of concurrent calls (somewhere between 30 to 100) can cause the software to fail and actually break the backed functionality.
As a work around I'd like to fix this by having my code block if more then X concurrent calls are outstanding, sending new calls only after the old calls resolve. As I already have a number of outstanding UIs I'd also prefer to change this behavior by updating a shared lower level Request method.
My 'request' method returns a promise, which is ultimately called within a redux Saga in each UI via the 'call' redux-effect. This means I need to be able to return a promise from my Request method, which means figuring some way to return a promise that won't try to resolve a Request until the earlier Requests resolve.
I started to write my own, but there is no easy way for me to 'alert' a promise, to tell it that previous promises have completed. The best way to roll my own I was able to figure out involved using Promise.race and then having the exact same callNextQueued method called for both the resolve and reject method, and generally it just feels kind of ugly conceptually.
Is there a better way to implement this? either an API that already does it, or a clean way to work with promises to role my own logic for starting a new request whenever any old request completes?

DOM update after async call

I am fetching some data from my server and updating the DOM through Angular two way bindning. It is however not working as expected and I need to wrap it inside an ugly setTimeout function for the DOM to have time to update.
$http.post('myBackend.php', someData)
.then(function(res){
$scope.data = res.data;
doStuffWithDOMElements(); // Does not work
});
while this works:
$http.post('myBackend.php', someData)
.then(function(res){
$scope.someDataToPopulateDOMwith = res.data;
setTimeout(function(){ doStuffWithDOMElements();}, 50); // Yup, works
});
The line that gives error without the timeout "Can't read propertly of null" is this:
let y_0 = document.getElementById("l_0").offsetTop;
and in my index.html
<div id="l_{{$index}}" ng-repeat = "x in data"></div>
This is weird. Shouldn't every DOM element that is wrapped up in Angular "events" be updated automatically? $scope.$apply() does not work and shouldn't be necessary either. What's wrong here?
The need of $timeout comes every once in a while in angularjs.Most probably to init a jQuery plugin.
Your Error line:
let y_0 = document.getElementById("l_0").offsetTop;
is because your DOM has not yet been set and you are trying to get the element which yet has to be set or rather rendered in the DOM.
When you use $timeout,it should run after the DOM has been manipulated by Angular, and after the browser renders (which may cause flicker in some cases).That is why it is working in your case when you set the $timeout.
If you want to learn more about digest cycle. Your should know about $evalAsync as well.
If code is queued using $evalAsync from a directive, it should run after the DOM has been manipulated by Angular, but before the browser renders
If code is queued using $evalAsync from a controller, it should run before the DOM has been manipulated by Angular (and before the browser renders) -- rarely do you want this
If code is queued using $timeout, it should run after the DOM has been manipulated by Angular, and after the browser renders (which may cause flicker in some cases).
FurtherMore, Angularjs is a javascript framework. A browser has to do a number of things pretty much all at once, and just one of those is execute JavaScript. But one of the things JavaScript is very often used for is to ask the browser to build a display element. This is often assumed to be done synchronously (particularly as JavaScript is not executed in parallel) but there is no guarantee this is the case and JavaScript does not have a well-defined mechanism for waiting.
The solution is to "pause" the JavaScript execution to let the rendering threads catch up. And this is the effect that setTimeout() with a timeout of 0 does. It is like a thread/process yield in C. Although it seems to say "run this immediately" it actually gives the browser a chance to finish doing some non-JavaScript things that have been waiting to finish before attending to this new piece of JavaScript.
(In actuality, setTimeout() re-queues the new JavaScript at the end of the execution queue. See the comments for links to a longer explanation.)
IE6 just happens to be more prone to this error, but I have seen it occur on older versions of Mozilla and in Firefox.
Also, a lot has been written and explained about why the use of $timeout comes in handy time to time.
Links where you will find good explanation:
Why is setTimeout(fn, 0) sometimes useful?
AngularJS : $evalAsync vs $timeout
Breakup the Operations
One of the things to do is breakup the operations by chaining them.
$http.post('myBackend.php', someData)
.then (function onFulfilled (response) {
$scope.someDataToPopulateDOMwith = response.data;
return response;
}).then (function onFulfilled2 (response) {
doStuffWithDOMElements();
});
This allows the $q service to execute a digest cycle before invoking the second fulfillment handler. The AngularJS framework needs to do a digest cycle so the watch handlers for the ng-repeat directive have an opportunity update the DOM. The ng-repeat directive needs to finish updating the DOM before the doStuffWithDOMElements function can safely manipulate the DOM.
Use $timeout Service
Avoid using the raw browser setTimeout function, instead use the $timeout service. The AngularJS $q service then automatically does digest cycles.
Since the $timeout service returns promises, it can be used for chaining.
$http.post('myBackend.php', someData)
.then (function onFulfilled (response) {
$scope.someDataToPopulateDOMwith = response.data;
return response;
}).then (function onFulfilled2 (response) {
//return $timeout promise
return $timeout(function(){return response}, 1000);
}).then (function onFulfilled3 (response) {
doStuffWithDOMElements();
});
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
Re-factor the Design
The AngularJS is a MVW* framework.
Avoid manipulating HTML DOM programmatically: Manipulating HTML DOM is a cornerstone of AJAX applications, but it's cumbersome and error-prone. By declaratively describing how the UI should change as your application state changes, you are freed from low-level DOM manipulation tasks. Most applications written with Angular never have to programmatically manipulate the DOM, although you can if you want to.2
Look into modeling what the doStuffWithDOMElements() function does and creating custom directives to watch that model and update the DOM. This fits better with the AngularJS framework and will avoid these timing issues.
in case of DOM manipulation in angular,it is well known.
see this SO link

Promise.resolve().then vs setImmediate vs nextTick

NodeJS 0.11 as well as io.js and the Node 0.12 branch all ship with native promises.
Native promises have a .then method which always executes on a future event loop cycle.
So far I've been using setImmediate to queue things to the next iteration of the event loop ever since I switched from nextTick:
setImmediate(deferThisToNextTick); // My NodeJS 0.10 code
process.nextTick(deferThisToNextTick); // My NodeJS 0.8 code
Since we now have a new way to do this:
Promise.resolve().then(deferThisToNextTick);
Which should I use? Also - does Promise.resolve.then act like setImmediate or like nextTick with regards to code running before or after the event loop?
Using Promise.resolve().then has no advantages over nextTick. It runs on the same queue, but have slightly higher priority, that is, promise handler can prevent next tick callback from ever running, the opposite is not possible. This behaviour is an implementation detail and should not be relied on.
Promise.resolve().then is obviously slower (a lot, I think), because it creates two promises which will be thrown away.
You can find extensive implementation info here: https://github.com/joyent/node/pull/8325
The most important part: Promise.resolve().then is like nextTick and not like setImmediate. Using it n place of setImmediate can change your code behaviour drastically.
I'm not going to answer the bolded part about technicalities, but only the question
Which should I use?
I don't think there is any reason to use Promise.resolve().then() unless you are interested in the promise for the result of your asynchronously executed function. Of course, if you are, then this would be far superior than dealing with callback hell or making a new Promise from setTimeout or nextTick.
There's also a second technical difference, more import than the timing: promises do swallow exceptions. Which you probably don't want. So, like #vkurchatkin mentioned, don't create promises only to throw them away. Not only because it's slower, but because it makes your code less readable and your app more error-prone.
Promise.resolve would be resolved straight away (syncroniously), while setImmediate explicitly straight after the execution of current event.

'Zombie promises' continuing after a mocha.js test timeout

I'm using a testing setup with Mocha.js and a lot of promises within the tests. The tests depend on setting up stuff in the DOM, and between tests, the DOM is cleared. However, sometimes the tests run slowly and time out. In this case, their promises continue to execute but the DOM is cleared before the next test, so the promise may incorrectly throw errors into the next test. Is there a way to cancel or destroy all outstanding promises in-between tests? We are using when.js promises.
when.js supports a cancel() method. You could call it from a afterEach or after block in mocha. You might need to create an array at the top of each mocha file (or as global) to track your outstanding promises.

Angularjs - Digest Loop/Repaint timing

having a small issue with AngularJS. See the following:
$http({method: 'GET', url: 'some/url/withJSON.json'})
.success(function(data)
{
$scope.data = data
$animations._load('arg1')
})
$animations is a registered service that does some stuff. Occasionally (maybe 20%) of the time the _load() method will run before the window has been painted/the digest loop completes, meaning the visualisation hasn't completed. By wrapping _load() with a setTimeout() of 10ms this has resolved it, but is there an implicit/explicit way in AngularJS to execute a callback once the digest loop has finished?
Without more information, I would suggest $timeout(fn) will be the best way to do this. It basically makes sure there's a break after angular completes processing for the browser to render. Roughly it is equivalent to setTimeout(fn, 0), so is what you have already suggested, but it is mockable for testing.
There is also $scope.$evalAsync(fn) that would normally render after the current digest cycle but before the DOM renders. I tend to prefer $evalAsync as it makes things easier for me to reason about, and won't allow other non-angular happenings to occur in the meantime, but it depends which works for you.
This answer has more detailed information about the two methods.

Categories