Rxjs: Import error when cancelAnimationFrame is not defined

Created on 6 Jan 2017  路  11Comments  路  Source: ReactiveX/rxjs

RxJS version:
15.0.3 (also encountered in 5.0.0-rc.1).

Code to reproduce:
I encountered this while writing jest tests for a react native app. I wrote a test that looks like this:

import 'react-native';
import rx from 'rxjs';

// ... the rest doesn't matter

That series of imports raised this error:

TypeError: Cannot read property 'bind' of undefined

at new RequestAnimationFrameDefinition (node_modules/rxjs/util/AnimationFrame.js:6:66)
at Object.<anonymous> (node_modules/rxjs/util/AnimationFrame.js:33:26)
at Object.<anonymous> (node_modules/rxjs/scheduler/AnimationFrameAction.js:8:24)
at Object.<anonymous> (node_modules/rxjs/scheduler/animationFrame.js:2:30)

Which led me to src/util/AnimationFrame:8:

function RequestAnimationFrameDefinition(root) {
    if (root.requestAnimationFrame) {
        this.cancelAnimationFrame = root.cancelAnimationFrame.bind(root); // <-- the failing line
        this.requestAnimationFrame = root.requestAnimationFrame.bind(root);
    }
// ...

It seems that under those circumstances, requestAnimationFrame is defined but cancelAnimationFrame is not, so it enters the if block and then throws. However, if rxjs is imported before react-native, everything is fine, presumably because react-native is what adds requestAnimationFrame to global.

I created an issue in the Jest repo with more details, and I've also created an example repo. If you clone that you should be able to see the error by running npm test.

Expected behavior:
Ideally, this should not attempt to bind cancelAnimationFrame unless it exists.

Actual behavior:
The line in question evaluates to undefined.bind and throws an error.

Additional information:
I realize this is a pretty specific situation, just thought I'd point out that in some instances it is possible for requestAnimationFrame to be defined and cancelAnimationFrame not to be.

Most helpful comment

If you are running into this in jest using Create React App, add

global.cancelAnimationFrame = function(callback) {
  setTimeout(callback, 0);
};

to your src/setupTests.js alongside your shim you're probably already running for requestAnimationFrame.

All 11 comments

Until now I was under assumption of requestAnimationFrame and cancelAnimationFrame available as pair, one can't be undefined without others.

So for this cases let's say if cancelAnimationFrame is not available, how does given scheduled one being cancelled via requestAnimationFrame? is there other pair supports those, or cancellation is just not supported in those environment?

As far as I can tell the jest/react-native situation was caused by some odd behavior in jests packager. In that situation I think cancellation may not be supported (and requestAnimationFrame may also just be stubbed out.)

I'm not sure that there is a "normal" situation where this could come up, I just thought I'd point out that in some edge cases this check can cause the rxjs import to fail. I don't necessarily think it's this library's responsibility to account for every possible environment, though it does seem like it would be a fairly straightforward fix to add a cancelAnimationFrame check before attempting to use it.

I guess it just comes down to philosophy. Either way, it's unlikely to affect many users.

@liamfd

At this moment, I wouldn't think add a cancelAnimationFrame check as an immediate fix for two reason - first, if any real environment those have those cases (which has request but no cancel) then RxJS's scheduler implementation will not able to cancel anything by adding those checks, should consider legit strategy to bring cancellation support. Secondly, if it's certain test-framework environment mutation-specific (i.e, jest-runtime) it's up to consumer / or library's call to support those correctly.

By your explanation, I have an initial feeling this might be similar cases to those since jest had similar cases before with RxJS when it does some mocking internally - would you able to confirm if jest+ react native does preserve its runtime environment correctly?

@kwonoj still looking into it, but it seems like it's only happening because of the interaction between the RN and Jest. When running jest without importing RN, requestAnimationFrame is not defined so this issue doesn't come up. When just running an RN app, both are defined, so there's no issue there either.

Didn't realize how complex it would be to try to implement the cancelAnimationFrame check in rxjs. Seems fair to close this then!

@liamfd just clarifying bit more - I'm not saying let's not fix RxJS :), would like to understand bit more 1. if this happens by jest 2. if it isn't, how to deal with canceling if given environment.

I'll leave this opened bit more, feel freely ping us if there's additional detail needed in Rx side codebases. It's bit hurry to close at this moment since it's not clear where this comes from.

If cancelAnimationFrame isn't available, I'd imagine we should fall back to setTimeout

@kwonoj sounds good, I just opened an issue in react-native so we'll see where that gets us. Thanks for the responses thus far!

I'm going to close issue this for now, as most environment should have pair of scheduling function. We can have fallback to setTimeout as separate PR if needed.

If you are running into this in jest using Create React App, add

global.cancelAnimationFrame = function(callback) {
  setTimeout(callback, 0);
};

to your src/setupTests.js alongside your shim you're probably already running for requestAnimationFrame.

@JHKennedy4 that worked for me! Thanks.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marcusradell picture marcusradell  路  4Comments

haf picture haf  路  3Comments

Zzzen picture Zzzen  路  3Comments

chalin picture chalin  路  4Comments

unao picture unao  路  4Comments