question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

IE11 "Unable to get property 'bind' of undefined or null reference - main.js (5,9402)" when using Promise ponyfill

See original GitHub issue

Setup:

  • Webpack + Babel.
  • I can’t touch the global scope, so I can’t polyfill.
  • I’m using Webpack Provide Plugin + native-promise-only-ponyfill for Promise support.

My code:

import { h, render, Component } from 'preact';

When I clicked the error, I found this in the Preact build:

e.prototype.then.bind(e.resolve())

It appears as though “e” is the minified name for “Promise”, and that the error is coming from around there in the code.

Is Preact intentionally touching the prototype of Promise? Would there be a reason for this? Or is this an artifact of a build tool?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:13 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
marvinhagemeistercommented, Feb 16, 2020

@jacobbogers @just-boris That’s a great suggestion and it is indeed a few bytes shorter than what we have now 👍 But we have to think about the big picture here. This piece of code is somewhat in the “hot path” as it’s called very frequently during the life of a Preact application. It’s not as hot as createElement/h, but still it will be called quite a lot. Because of that any changes will affect benchmarks here.

// Assumption: One time allocation
const defer = Promise.prototype.then.bind(Promise.resolve());

// Assumption: Allocates a new Promise whenever it is called
const defer = fn => Promise.resolve().then(fn);

An additional allocation here will affect benchmarks negatively like this one: https://github.com/krausest/js-framework-benchmark . One can of course rightfully argue about how meaningful those numbers are, but many developers still make decisions based on those numbers. The .bind approach seems to armortize that cost for us and the few benchmarks we have in our repo seem to back that up. There the bind() approach is slightly faster on my machine. So we trade a few bytes for runtime performance here.

Optimizations change frequently in browsers, so this may not be this way for forever. Just to be sure I posted the same question on Twitter to some v8 engineers.

3reactions
marvinhagemeistercommented, Feb 16, 2020

In Preact we use that trick to process our global queue in “ticks”. The queue holds components that need to be rendered or otherwise updated. There are 3 ways components are pushed into the queue:

  1. component.setState()
  2. component.forceUpdate()
  3. render(<App/>, container)

Note that even our hooks implementation makes use of classes internally and will call the same setState or forceUpdate function on the backing class of a component.

Once the queue has some items in it, we need to start processing it. But we don’t want to do that immediately as the current diff should finish first, so that all dirty components of the same “commit” are processed at the same time. So we need to somehow schedule the invocation of when we should start processing it.

An easy way to do that in all browsers is via setTimeout. It fullfills our requirement to defer processing the queue until the current synchronous diff is completed.

// Warning: Pseudo code!
const queue = [A, B, C]

function process() {
  let item;
  while ((item = queue.pop()) {
    // some processing logic. Likely applying
    // changes to the DOM or something.
    doSomething(item);
  }
}

function enqueueRender(component) {
  queue.push(component)
  // TODO: Missing here is a way to check if the processing
  // is already running
  process();
}

// Somewhere called in setState or forceUpdate
enqueueRender(currentComponent)

Now on more modern browser we do have more options on how we can schedule work. One of those is via so called microtasks. It’s quicker and in our benchmarking tests more performant compared to setTimeout on modern browsers. To do that we have to leverage the Promise object. In a similar fashion to setTimeout we need to have a function that works the same way, but uses Promises under the hood.

Calling just Promise.prototype.then() directly will throw as a Promise expects to act on a receiving Promise that is passed via the this argument. To check that one can fire up node and paste the following lines:

> Promise.prototype.then(() => console.log("foo"))
Thrown:
TypeError: Method Promise.prototype.then called on incompatible receiver #<Promise>
    at Promise.then (<anonymous>)
> Promise.prototype.then.bind(Promise.resolve())(() => console.log("foo"))
Promise { <pending> }
> foo

As you can see above, calling Promise.prototype.then directly throws an error. Therefore we need to bind some Promise object to the this value of the .then method. We can do that via the .bind() operator, which brings us to the code that we use in Preact today:

// Function to schedule the queue processing
const defer =
	typeof Promise == 'function'
		? Promise.prototype.then.bind(Promise.resolve())
		: setTimeout;

So in conclusion the main reason we use Promises instead of setTimeout to process our queue is performance. Hope that answers your questions 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular2 IE11 Unable to get property 'apply' of undefined or ...
Angular has dependency on core-js. Thereby you can use Object.assign polyfills from it:
Read more >
How To Get Property 'Bind' Of Undefined. Babel 7 + Core-Js + Ie11
Ask questionsIE11 Unable to get property 'bind' of undefined or null reference main.js 59402 when using Promise ponyfill. Setup: Webpack + Babel.
Read more >
IE11 Unable to get property 'apply' of undefined or null reference
We had exactly the same error and today everything started to work fine again. I'm looking for some information about updates or error...
Read more >
IE11 Dialog Promise Undefined - The Aurelia Discourse
I'm in the process of updating an existing app using (Aurelia Framework 1.1.4) to the latest version of Aurelia.
Read more >
Fix Cannot Set Property of Null Error in JavaScript - YouTube
Check us out at https://www.skillforge.com The "cannot set property of null " error is a very common JavaScript issue that you may come ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found