I've got this code:
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve({dogs: ['Fido', 'Spot']});
}, 2000);
});
p1.then(function (val) {
console.log('first then');
console.dir(val);
return _.extend(val, {cats: ['Fluffy', 'Whiskers']});
}).then(function (val) {
console.log('second then');
console.dir(val);
});
The unexpected console output shows:
I don't understand how cats could possibly be part of the value before it's actually appended into the object. The results printed in the second then make sense to me though. Am I missing something?
You're adding the cats property to the same object you already logged.
As the i icon tells you, the console only reads the properties of the object when you actually expand it.
As far as I know there is a bug in console.log by ES6 Promises. It waits for a few seconds before it logs the value of your object, that's why it contains the cats. If I remember well, this bug happens in firefox. I don't know how it behaves by the other browsers.
Related
About two months ago we started using Rollbar to notify us of various errors in our Web App. Ever since then we have been getting the occasional error:
ResizeObserver loop limit exceeded
The thing that confuses me about this is that we are not using ResizeObserver and I have investigated the only plugin which I thought could possibly be the culprit, namely:
Aurelia Resize
But it doesn't appear to be using ResizeObserver either.
What is also confusing is that these error messages have been occuring since January but ResizeObserver support has only recently been added to Chrome 65.
The browser versions that have been giving us this error are:
Chrome: 63.0.3239 (ResizeObserver loop limit exceeded)
Chrome: 64.0.3282 (ResizeObserver loop limit exceeded)
Edge: 14.14393 (SecurityError)
Edge: 15.15063 (SecurityError)
So I was wondering if this could possibly be a browser bug? Or perhaps an error that actually has nothing to do with ResizeObserver?
You can safely ignore this error.
One of the specification authors wrote in a comment to your question but it is not an answer and it is not clear in the comment that the answer is really the most important one in this thread, and the one that made me comfortable to ignore it in our Sentry logs.
This error means that ResizeObserver was not able to deliver all observations within a single animation frame. It is benign (your site will not break). – Aleksandar Totic Apr 15 at 3:14
There are also some related issues to this in the specification repository.
It's an old question but it still might be helpful to someone. You can avoid this error by wrapping the callback in requestAnimationFrame.
For example:
const resizeObserver = new ResizeObserver(entries => {
// We wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return;
}
// your code
});
});
If you're using Cypress and this issue bumps in, you can safely ignore it in Cypress with the following code in support/index.js or commands.ts
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopErrRe.test(err.message)) {
return false
}
})
You can follow the discussion about it here.
As Cypress maintainer themselves proposed this solution, so I believe it'd be safe to do so.
We had this same issue. We found that a chrome extension was the culprit. Specifically, the loom chrome extension was causing the error (or some interaction of our code with loom extension). When we disabled the extension, our app worked.
I would recommend disabling certain extensions/addons to see if one of them might be contributing to the error.
For Mocha users:
The snippet below overrides the window.onerror hook mocha installs and turns the errors into a warning.
https://github.com/mochajs/mocha/blob/667e9a21c10649185e92b319006cea5eb8d61f31/browser-entry.js#L74
// ignore ResizeObserver loop limit exceeded
// this is ok in several scenarios according to
// https://github.com/WICG/resize-observer/issues/38
before(() => {
// called before any tests are run
const e = window.onerror;
window.onerror = function(err) {
if(err === 'ResizeObserver loop limit exceeded') {
console.warn('Ignored: ResizeObserver loop limit exceeded');
return false;
} else {
return e(...arguments);
}
}
});
not sure there is a better way..
add debounce like
new ResizeObserver(_.debounce(entries => {}, 200);
fixed this error for me
The error might be worth investigating. It can indicate a problem in your code that can be fixed.
In our case an observed resize of an element triggered a change on the page, which caused a resize of the first element again, which again triggered a change on the page, which again caused a resize of the first element, … You know how this ends.
Essentially we created an infinite loop that could not be fitted into a single animation frame, obviously. We broke it by holding up the change on the page using setTimeout() (although this is not perfect since it may cause some flickering to the users).
So every time ResizeObserver loop limit exceeded emerges in our Sentry now, we look at it as a useful hint and try to find the cause of the problem.
In my case, the issue "ResizeObserver - loop limit exceeded" was triggered because of window.addEventListener("resize" and React's React.useState.
In details, I was working on the hook called useWindowResize where the use case was like this const [windowWidth, windowHeight] = useWindowResize();.
The code reacts on the windowWidth/windowHeight change via the useEffect.
React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setWidgetHeight(h),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
So any browser window resize caused that issue.
I've found that many similar issues caused because of the connection old-javascript-world (DOM manipulation, browser's events) and the new-javascript-world (React) may be solved by the setTimeout, but I would to avoid it and call it anti-pattern when possible.
So my fix is to wrap the setter method into the setTimeout function.
React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setTimeout(() => setWidgetHeight(h), 0),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
One line solution for Cypress. Edit the file support/commands.js with:
Cypress.on(
'uncaught:exception',
(err) => !err.message.includes('ResizeObserver loop limit exceeded')
);
https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/resize_observer/resize_observer_controller.cc#L44-L45
https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/frame/local_frame_view.cc#L2211-L2212
After looking at the source code, it seems in my case the issue surfaced when the NotifyResizeObservers function was called, and there were no registered observers.
The GatherObservations function will return a min_depth of 4096, in case there are no observers, and in that case, we will get the "ResizeObserver loop limit exceeded" error.
The way I resolved it is to have an observer living throughout the lifecycle of the page.
Managed to solve this in React for our error logger setup.
The Observer error propagates to the window.onerror error handler, so by storing the original window.onerror in a ref, you can then replace it with a custom method that doesn't throw for this particular error. Other errors are allowed to propagate as normal.
Make sure you reconnect the original onerror in the useEffect cleanup.
const defaultOnErrorFn = useRef(window.onerror);
useEffect(() => {
window.onerror = (...args) => {
if (args[0] === 'ResizeObserver loop limit exceeded') {
return true;
} else {
defaultOnErrorFn.current && defaultOnErrorFn.current(...args);
}
};
return () => {
window.onerror = defaultOnErrorFn.current;
};
}, []);
I had this issue with cypress tests not being able to run.
I found that instead of handling the exception the proper way was to edit the tsconfig.json in a way to target the new es6 version like so:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "../node_modules",
"target": "es5", --> old
"target": "es6", --> new
"types": ["cypress", "#testing-library/cypress"],
"sourceMap": true
},
"include": [
"**/*.ts"
]
}
I am creating an element (a router - but that is not important), that is scanning the DOM soon after it has attached for particular other custom elements. I certain cases it needs to throw an error and I want to test for these.
The test I constructed is not failing - but as far as I can make out the test has already failed before my element gets attached. I suspect it is the asynchronous nature of things.
Here is the snippet of the test in question. The test fixture in question contains elements that will cause one of the elements to fail after a 'dom-change' event happens (which it has a listener for) when it then scans the dom for other things.
it('should fail if two route elements both designate thenselves as home', function(done) {
var t= document.getElementById('multiple_home');
function multiple () {
t.create();
}
expect(multiple).to.throw(Error);
t.restore();
done();
});
I think the problem is related to the fact that the fixture is created in multiple, but hasn't yet failed by the time multiple exits. I am wondering if I can pass a Promise to expect - except I am not sure how to turn mulitple into a Promise to try it out.
I eventually found a way, but it requires instrumenting the element a bit to support this.
In the elements "created" callback I create a Promise and store the two functions to resolve and reject it in "this" variables - thus:-
this.statusPromise = new Promise(function(resolve,reject){
this.statusResolver = resolve;
this.statusRejector = reject;
}.bind(this));
In the DOM parsing section I use a try catch block like this
try {
//parse the dom throwing errors if anything bad happens
this.statusResolver('Any useful value I like');
} catch (error) {
this.statusRejector(error);
}
I then made a function that returns the promise
domOK: function() {
return this.statusPromise;
}
Finally in my test I was now able to test something like this (I load the fixture in each test, rather than a beforeEach, because I am using a different fixture for each test. I do clear it down again in an afterEach). Note the use of the .then and .catch functions from the Promise.
it('should fail if two route elements declare the same path name',function(done){
t = document.getElementById('multiple_path');
t.create();
r = document.getElementById('router')
r.domOK().then(function(status){
//We should not get here throw an error
assert.fail('Did not error - status is: ' + status);
done();
}).catch(function(error){
expect(error.message).to.equal('There are two nodes with the same name: /user');
done();
});
I'm trying to understand the es6 Promises. As I understood, they can be chained to be executed sequentially. It does not work in by case.
console.log("Started");
function doStuff(num, timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("now " + num);
resolve();
}, timeout);
});
}
doStuff(1, 3000).then(doStuff(2, 2000)).then(doStuff(3, 1000));
However the output is:
$ node test
Started
now 3
now 2
now 1
I was expecting the reverse order. I do understand why it gets like this, they are all queued up and finishes in the "reverse" order.
But the thing is, I thought that the second was not executed until the first was finished and so on. What am I missing?
If you write it like this, the 3 calls to doStuff will start when you write the line. You have to write it like this :
doStuff(1, 3000).then(function() {
return doStuff(2, 2000);
}).then(function() {
return doStuff(3, 3000);
});
As loganfsmyth said, is you are doing ES6, you can also use arrow functions :
doStuff(1, 3000).then(() => doStuff(2, 2000)).then(() => doStuff(3, 3000));
Isn't there a typo ? you should chain the then part to the doStuff call, perhaps like this:
doStuff(1, 3000).then(function(){
doStuff(2, 2000).then(function(){
doStuff(3, 1000);
});
});
timeouts in javascript are asynchronous. The way you have it written now, all three promises are executed in order, and the timeout function just queues up the code inside of it to run after a certain time duration. A timeout's execution doesn't mean its resolution; it's considered "done" when its internal code is queued. That's why the second and third promises don't have to wait for the line "console.log("now " + num);" to execute before being kicked off.
See this answer https://stackoverflow.com/a/19626821/2782404 for some background on asynchronous tasks in js.
I'm working on my first react/reflux app so I may be approaching this problem in completely the wrong way. I'm trying to return a promise from a reflux store's action handler. This is the minimum code that represents how I'm trying to do this. If I display this in the browser, I get an error saying that the promise is never caught, because the result of the onLogin function is not passed back when the action is initiated. What is the best way to do this?
var Reflux = require('reflux');
var React = require('react/addons')
const Action = Reflux.createAction();
const Store = Reflux.createStore({
init: function() {
this.listenTo(Action, this.onAction);
},
onAction: function(username, password) {
var p = new Promise((resolve, reject) => {
reject('Bad password');
});
return p;
}
});
var LoginForm = React.createClass({
mixins: [Reflux.connect(Store, 'store')],
login: function() {
Action('nate', 'password1').catch(function(e) {
console.log(e); // This line is never executed
});
},
render: function() {
return (
<a onClick={this.login} href="#">login</a>
)
}
});
React.render(<LoginForm />, document.body);
Several things seem a bit confused here.
Reflux.connect(Store, 'store') is a shorthand for listening to the provided store, and automatically set the "store" property of your component state to whatever is passed in your store's this.trigger() call. However, your store never calls this.trigger so "store" in your component's state will never be updated. Returning a value from your store's action handlers doesn't trigger an update.
Stores should listen to actions to update their internal state, and typically then broadcast this state update by calling this.trigger. No component is going to get your returned promise from the store's onAction unless it explicitly calls Store.onAction (and then it doesn't matter if the actual action was invoked or not).
Async work should typically happen in the action's preEmit hook, not in the store. You should then also declare the action as async in createAction by setting the asyncResult option to true to automatically create "completed" and "failed" child actions. Check out the Reflux documentation here to learn about async events. Async actions automatically return promises, whose resolve and reject are called when the "completed" and "failed" sub-actions are called respectively. This is a bit opinionated, but that is definitely what I perceive is the intended Reflux way.
In jQuery, if you make a mistake in your ajax callback method, you will get a proper console error message and stacktrace.
$.get("https://api.github.com/users/octocat/orgs", function() {
var a = FAIL;
});
However, in dojo using dojo/request/xhr it seems these dumb mistakes are being swallowed completely. The only thing in my console when I run this is "then" and "always".
require(["dojo/request/xhr" ], function(xhr) {
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(function(data) {
console.log('then');
var a = FAIL;
console.log('goodbye');
}, function() {
console.log('error');
});
promise.otherwise(function() {
console.log('otherwise');
});
promise.always(function() {
console.log('always');
});
});
Using the deprecated dojo.xhrGet, the problem is very slightly improved. I get a console error message and my error handler is called but it only says "ReferenceError {}" and provides me with a stack trace that never points to a function I own:
dojo.xhrGet({
url: "https://api.github.com/users/octocat/orgs",
load: function() {
console.log('dojo.xhrGet.load');
var a = FAIL;
console.log('goodbye dojo.xhrGet.load');
},
error: function() {
console.log('dojo.xhrGet.error');
},
handle: function() {
console.log('dojo.xhrGet.handle');
}
});
When writing a program we make mistakes, it's nice that we have tools like chrome developer tools to point us to those mistakes. The time it takes to find an error when you can see a stacktrace and error message is obviously much quicker than if you get no feedback. I get no feedback in dojo, I can't believe that such a popular library could operate in this way. What am I doing wrong?
The understanding of promises which you inherited from jQuery is fundamentally different to the one everyone else (check Promises/a+ implementations) has. For the rest of this answer I will talk about promises/a+ compliant promises. Dojo's Deferred actually isn't a+ compliant, but it's close enough that everything I discuss here applies equally well.
Promises are immutable, you cannot change a promises state by calling then. A promise represents an eventual value, it would be nonsensical to be able to change the promise by saying "once the value is ready, do this".
So then, hopefully that explains why your error handler is not invoked, but the basic idea, of catching errors, is still totally possible. You just need to use return values. When you call then on a promise, it returns a new and (almost always) different promise. This new promise is very special, if the original is resolved, and the success handler you passed is invoked, and that returns something, that something will be the resolution value of the second promise.
Equally, if the error handler (on the first promise) is triggered, and that function returns something, that something will be the resolution value of the second promise. The same is true for thrown errors, they are passed to the error handler (of the second promise!).
So here's your first code sample written in a more promises/a+ way :
require(["dojo/request/xhr" ], function(xhr) {
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(function(data) {
console.log('then');
var a = FAIL;
console.log('goodbye');
}, function() {
console.log('error');
}).then(null, function() {
console.log('otherwise');
});
promise.always(function() {
console.log('always');
});
});
I don't really understand what you want to do with the always function, so I wasn't sure where to place that one. On the subject of call stacks, I would recommend checking out the Q promise library which has incredibly advanced asynchronous call stack support.
In dojoConfig set useDeferredInstrumentation: true. Here's an example.
<script>
var dojoConfig = {
useDeferredInstrumentation: true
};
</script>
<script src="js/lib/dojo/dojo.js.uncompressed.js"></script>
This gives a fairly functional error message and stacktrace output on console.error:
ReferenceError {} "ReferenceError: FAIL is not defined
at http://fiddle.jshell.net/gNdCb/2/show/:25:17
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14205:21)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14220:6)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14226:4)
----------------------------------------
rejected at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14252:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14223:5)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14220:6)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14226:4)
----------------------------------------
Error
at Promise.then.promise.then (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14420:24)
at http://fiddle.jshell.net/gNdCb/2/show/:23:13
at runFactory (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1117:43)
at execModule (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1245:5)
at http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:812:7
at guardCheckComplete (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1260:5)
at contextRequire (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:811:6)
at req (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:137:11)
at http://fiddle.jshell.net/gNdCb/2/show/:21:1"
I had very specific needs in that I needed the exception to hit the native catch clause that the browser implements. Nevermind why I needed this, But I used something like this:
function scream(func) {
return function() {
var args = arguments;
setTimeout(function(){
func.apply(null, args);
}, 0);
};
}
Then, to use it
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(scream(function(data) {
//do stuff
}));
By using setTimeout, you execute the function on the browsers event queue, making it impossible for dojo to swallow your exception. But, in general this is a bad solution because:
it changes part of the stack trace
it changes a part of your code which previous executed synchronously to asynchronously, which can change program behavior
you can't chain multiple .then() promise objects to the return value, which is one of the really nice things about promises.
Anyway, I'm just presenting it as option.