How to manually abort an action
Independent of Lightning, the behavior you've proposed doesn't work in a client-server setup over HTTP . Once the request gets to the network you've no reliable means to make it a noop. Eg the server may already have received the request and started processing it so killing the HTTP connection isn't useful.
It's non-trivial but not impossible to achieve the same objective. It's non-trivial because the operations involved (the network request, server processing) are asynchronous and don't guarantee order across requests. Eg just because request 2 goes out after request 1 doesn't mean that request 2 is received and processed after request 1.
Instead consider: 1. Add a delay after user input and before you enqueue the action. This is often called debouncing the event stream. This'll get you pretty good behavior. 2. To ensure the last action is processed by the server after all previous actions you need to wait for all previous actions to return before enqueuing the last action. This'll ensure correct behavior in all circumstances.
Here is what I think is the correct answer if Salesforce doesn't provide us a better way to do it.
EDIT : I did include your (1) to the code. Thanks to @Kevin V
EDIT 2 : The code was not Locker Service compliant, now it is.
Helper
({
uniqueSearchId: null,
waitingId: null,
makeSearch: function(component, event, helper) {
var action = component.get('c.search');
var searchId = (new Date()).getTime();
this.uniqueSearchId = searchId;
action.setParams({
// Whatever param
});
action.setCallback(this, function(a) {
if (this.uniqueSearchId == searchId && component.isValid()) {
// Do something with the response
// like setting the response to a component
} else {
// Action aborted because another has been fired
// Or the component is not valid anymore
}
});
window.clearTimeout(this.waitingId);
this.waitingId = window.setTimeout($A.getCallback(function() {
if (component.isValid()) {
$A.enqueueAction(action);
}
}), 250);
}
})