Once #697
Once #697
Conversation
I'm afraid I still don't think this is a common enough use case to bake into Backbone.Events ... but you can use |
+1 for a proper .once() method which removes the event listener; could help prevent memory leaks |
+1 for this. @jashkenas - it's not the same thing since
For example:
|
+1 @jashkenas please re-open this for consideration. _.once does not provide the same functionality |
I would, but GitHub seems to have disappeared the "reopen" button that used to be on these pages. If the button comes back, I'd be glad to reopen it. |
I re-opened this issue and rebased to merge cleanly. A few other things as well: I updated the source code to not modify the original callback that was passed in. This previous method didn't sit well with me, because it meant code like this wouldn't work: var obj = _.extend({}, Backbone.Events);
var test = function() { console.log("triggered"); };
obj.once("test", test);
// Would not work.
obj.off("test", test); This new method simply binds an additional "cleanup" event that removes immediately after the first event fires. @jashkenas @braddunbar code review of this would be awesome. I also updated the qunit tests to use |
I like this one too, use it quite regularly. +1 |
// Bind the original event. | ||
this.on(events, callback, context); | ||
// Unbind the previous event and this after it is invoked. | ||
this.on(events, unbind, context); |
braddunbar
Sep 6, 2012
Collaborator
I believe the above should be this.on(events, unbind, this)
.
var context = {};
obj.on('event', function(){}, context);
// Dies because `unbind` is called with `context` instead of `obj`.
obj.trigger('event');
Also, I think the convention would be to return this
, though I'm tempted to use the simpler form and return unbind
for offing the handler.
I believe the above should be this.on(events, unbind, this)
.
var context = {};
obj.on('event', function(){}, context);
// Dies because `unbind` is called with `context` instead of `obj`.
obj.trigger('event');
Also, I think the convention would be to return this
, though I'm tempted to use the simpler form and return unbind
for offing the handler.
// Bind an event like `on`, but unbind the event following the first trigger. | ||
once: function(events, callback, context) { | ||
// Ensure proper context. | ||
context = context || this; |
braddunbar
Sep 6, 2012
Collaborator
No need to default context
here since it's done for us in trigger
.
No need to default context
here since it's done for us in trigger
.
Mornin' @tbranyen! The approach is interesting but I have to wonder, given the way model.on('event', function handler() {
model.off('event', handler);
// ...
}, model);
// ...
if (condition) model.off('event', handler); |
@braddunbar Is it a common pattern to use a NFE inside of event binding to emulate once? Personally I haven't seen this, but a cursory glance at EventEmitter, EventEmitter2, Emitter, and jQuery all have a |
Good question. I only used the NFE because it was easier to type out in a comment. |
@tbranyen +1. It's very convenient for setting off one time operations that need to respond once to a recurring event, such as a set up operation. |
@tbranyen Interesting. I hadn't considered the use of case of .off'ing a .once. I can see where you might want this flexibility but now we have 2x overhead for .once (2 .on handlers). I suppose I'd be willing to make that trade though :) |
@nibblebot They are synchronous calls, the overhead isn't as bad at what you'd think. It's basically an additional function call which we were already doing. |
@braddunbar I changed the implementation around, does this meet your concerns? There is almost no overhead with this new approach. |
@tbranyen Actually, I prefer the previous version. I think it's rather elegant and I'm ok with the extra call to I know the extra cost to Thanks for the discussion and my apologies for the back and forth. |
@braddunbar did we determine if there'd be any potential issues with the NFE? |
I've looked through the NFE bugs and I don't see any potential issues, but I wouldn't mind if we broke it out just to be safe. |
NFE leak in IE < 9 because it creates 2 distinct function; 1 function declaration and 1 function expression. |
|
Just found another place in my code in which // Special cases for when a parent View that has not been rendered is
// involved.
if (manager.parent && !manager.parent.__manager__.hasRendered) {
return manager.parent.on("afterRender", function() {
// Unbind this event... really wish we had once.
manager.parent.off(null, null, this);
// Trigger the afterRender and set hasRendered.
completeRender();
}, this);
} |
Another situation where it would be much cleaner to have once: ctor.prototype.fetch = function(options) {
// Set the nSync property to true indicating we are doing something.
this.nSync = true;
function reallyWantOnce() {
this.off("reset", reallyWantOnce);
this.nSync = false;
}
this.on("reset", reallyWantOnce, this); |
Definitely would be interesting for me like I already said in #594 |
@tbranyen I sent you a PR (tbranyen#1) that accounts for |
Just another word of support: I would love the |
+1 |
+1 |
Anything I can do to get this merged? @jashkenas @braddunbar ? |
@tbranyen please take a look at my pull request on your branch. It prevents an easily avoidable more-than-once "once" case. |
Awesome, updated @caseywebdev |
No prob! I love |
@tbranyen I posted some comments above, though I'll leave the merging to @jashkenas.
Another reason for the previous version, is that the current version fails in the following instance: var a = new Backbone.Model().once('event', f);
var b = new Backbone.Model().on('event', f);
a.trigger('event'); // Calls `f`
a.trigger('event'); // Noop
b.trigger('event'); // Calls `f`
b.trigger('event'); // Should call f, but doesn't I submitted a pull request with a failing test case. |
+1 |
… callback after the first time its called. Similar to jQuery one and Node.js emitter.once. See issue #156 for requests, discussion, and alternative implementation.
…id wrapping the original event callback
@jashkenas @braddunbar Let me know if you have any further concerns before we merge it. |
for (i = 0, length = list.length; i < length; i += 3) { | ||
// Remove the special `once` event immediately before triggering. | ||
if (list[i + 2]) { | ||
delete calls[event]; |
braddunbar
Dec 4, 2012
Collaborator
Removing all callbacks for the event isn't appropriate here. The other events aren't necessarily once
callbacks. Pull request with failing test case incoming.
Removing all callbacks for the event isn't appropriate here. The other events aren't necessarily once
callbacks. Pull request with failing test case incoming.
var incrB = function(){ obj.counterB += 1 }; | ||
obj.once('event', incrA); | ||
obj.once('event', incrB); | ||
debugger; |
tgriesser
Dec 7, 2012
Collaborator
leftover debugger
leftover debugger
@@ -153,15 +162,21 @@ | |||
|
|||
// Execute event callbacks. | |||
if (list) { | |||
console.log(calls[event]); |
braddunbar
Dec 7, 2012
Collaborator
snip
snip
+1 - I've run into several cases where this would have been helpful to have. |
Alright folks -- I've just pushed a patch that implements "once". Please take a very close look at it -- I think it's an elegant little implementation -- and add any additional test cases that you think I might be missing. |
once method