Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

makes event listener callbacks execute asynchronously #902

Merged
merged 5 commits into from
Jan 6, 2019

Conversation

rovolution
Copy link
Collaborator

@rovolution rovolution commented Dec 22, 2018

Background

After diving through our source code and tests, I realized a serious issue...none of our event listeners were executing asynchronously!

Though we were exposing a callback based API, the callbacks were being executed synchronously whenever the private method _trigger() was being executed.

I wrapped the callback invocations in setTimeout() with a timeout of 0 (which cues the callback to be invoked on the next event loop tick) and fixed a bunch of our tests to reflect this.

Pull Requests

Please accompany all pull requests with the following (where appropriate):

  • unit tests (we use Jasmine 2.x.x)
  • JSFiddle (or an equivalent such as CodePen, Plunker, etc) or screenshot/GIF with new feature or bug-fix
  • Link to original Github issue (if this is a bug-fix)
  • documentation updates to README file
  • examples within /tpl/index.tpl (for new options being added)
  • Passes CI-server checks (runs automated tests, JS source-code linting, etc..). To run these on your local machine, type grunt test in your Terminal within the bootstrap-slider repository directory

Copy link
Collaborator

@jespirit jespirit left a comment

Choose a reason for hiding this comment

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

Nice work! I'm surprised you fix all the tests that were failing. I expected a lot more to fail.

test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Outdated Show resolved Hide resolved
test/specs/DraggingHandlesSpec.js Show resolved Hide resolved
@jespirit
Copy link
Collaborator

For future reference:

setTimeout() with delay of 0. Under Late timeouts:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified

@rovolution
Copy link
Collaborator Author

@jespirit Thanks for the review! i updated with feedback. take a glance and let me know if it all looks good.

@rovolution rovolution removed the request for review from seiyria December 22, 2018 19:24
Copy link
Collaborator

@jespirit jespirit left a comment

Choose a reason for hiding this comment

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

Upon further review, I think we should revert the changes to how the callbacks are called and instead change the unit tests so they are executed asynchronously when callbacks are expected to be called.

expect(spy).not.toHaveBeenCalled();
done();
});
}, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Syntax error: }, 0); added argument 0 to it() instead of window.setTimeout()

});

it("'slideStop' event is triggered properly and can be binded to", function() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

$.trigger() is not async

I have removed the done argument and this test should fail because the 'slideStop' callback should be executed asynchronously. However, this test will still pass because _trigger() will call $.trigger() synchronously, and .trigger() will call custom events synchronously, with the exception of built-in events, which are executed sync/async depending on browser.

_triggerJQueryEvent: function(evt, val) {
var eventData = {
type: evt,
value: val
};
this.$element.trigger(eventData);
this.$sliderElem.trigger(eventData);
},

See this thread: https://stackoverflow.com/questions/14017013/is-the-jquery-trigger-function-guaranteed-to-be-synchronous

it("'slideStop' event is triggered properly and can be binded to", function() {
  touch.initEvent("touchstop");
  testSlider.on('slideStop', function() {  // this will be called synchronously
    expect(true).toBeTruthy();
  });
  testSlider.data('slider')._mouseup(mouse);
  window.setTimeout(function() {
    expect(spy).toHaveBeenCalled();
    // done();
  }, 0);
});

Tests Should Be Async

Upon further review,

I think the issue is not that the callbacks are being executed synchronously, I think the way we had it before was fine, it's just that the tests themselves were not being executed asynchronously.

So I think the tests should be reorganized to be as follows:

// Register callbacks
testSlider.on(/* slideStart, slide, slideStop */, function() { /* code here */});

setTimeout(function() {
  // set up code here to be executed asynchronously (next tick of the event loop)
}, 0);

For example,

it("Should trigger change on mouseup", function(done) {
  var changes = 0;

  // Register callbacks
  testSlider.on('slideStop', function(){
    expect(changes).toBe(2);
    expect(testSlider.getValue()).toEqual([2, 5]);
    done();
  });
  
  testSlider.on('change', function(){
    changes++;
  });

  // Execute this code async (on next tick of the event loop)
  setTimeout(function() {
    testSlider.mousedown(mouseEvent('mousedown', tickOffsets[1]));
    expect(testSlider.getValue()).toEqual([1, 5]);

    testSlider.mouseup(mouseEvent('mouseup', tickOffsets[2]));
  }, 0);
});

If you remove the call to done() then the test will timeout.

Drag handles over each other and use keyboard to move handles over each other
     √ should drag and keydown handles properly to the right then back to the left
     √ should drag and keydown handles properly to the left then back to the right
   √ Should snap to a tick within tick bounds when using the mouse navigation
   × Should trigger change on mouseup
     Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. (1)

for(let i = 0; i < callbackFnArray.length; i++) {
let callbackFn = callbackFnArray[i];
window.setTimeout(() => {
callbackFn(val);
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my review comment below, the callbacks should be executed synchronously at the moment they're triggered, which is how $.trigger() behaves.

Copy link
Collaborator Author

@rovolution rovolution Dec 28, 2018

Choose a reason for hiding this comment

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

thanks for pointing this out!

I reflected on this a bit more and I agree with you. Also, when a user triggers a mouse or keyboard based sliding event, this automatically triggers the necessary callbacks in an async fashion via the browser's native event handling mechanism.

If a user directly changes the value via the setValue() call, then the event handlers should execute in a sync fashion (as you indicated).

I looked at another popular slider library called noUiSlider, and this is how they do it as well.

@rovolution
Copy link
Collaborator Author

@jespirit just removed the changes + removed the default timeout amount of 0 in the unit tests (setTimeout defaults to 0 seconds when a specific timeout is not specified)

Copy link
Collaborator

@jespirit jespirit left a comment

Choose a reason for hiding this comment

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

It seems like every time I look at this pull request, there's something I missed. I left my review comments, take a look.

test/specs/EventsSpec.js Outdated Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Outdated Show resolved Hide resolved
test/specs/EventsSpec.js Outdated Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
test/specs/EventsSpec.js Show resolved Hide resolved
@rovolution
Copy link
Collaborator Author

@jespirit feel free to make the changes you want if you don't want me to block you. i probably will not have time to work on this

@jespirit jespirit merged commit 48dc375 into master Jan 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants