Why does $.when().pipe().then() work, but not $.when().then().then()? - javascript

I'm still trying to wrap my head around using JQuery's Deferred objects, and am scratching my head at one particular problem. In the following code, I initially tried to chain deferred.then() but it never worked. All three functions execute at once. Only after my co-worker pointed me to the pipe function did things fall into place. Question is, why does pipe() work, but not then()?
var otherDefer = function(msg){return function(){return testDefer(msg)}};
var there = otherDefer("there,");
var guy = otherDefer("guy.");
function testDefer(msg) {
var deferred = $.Deferred();
pretendAjaxCall( function() {
$('<li>'+msg+'</li>').appendTo('#msgOut');
deferred.resolve();
});
return deferred.promise();
}
function pretendAjaxCall(callback) {
setTimeout(callback,1500);
}
$.when(testDefer("Hi")).pipe(there).then(guy);​
I also tried return deferred instead of return deferred.promise() when using when().then().then().
jsFiddle for above code: http://jsfiddle.net/eterpstra/yGu2d/

Since jQuery 1.8 then() returns a new Promise (the same as pipe()) instead of the same Deferred that when() returns.
Change the jQuery version to 1.8.3 or above in your example at:
http://jsfiddle.net/eterpstra/yGu2d
and
$.when(testDefer("Hi")).then(there).then(guy);
will work.

This is how then() and pipe() work in your sample:
then() returns Deferred and by calling then() on this same Deferred you simply add a second callback to it which will be called simultaneously with the first one
pipe(), instead, returns new Promise allowing you to build a chain and that's why you get sequential calls in this case
Take a look at the following resources for more info about pipe/then:
When should I use jQuery deferred's "then" method and when should I use the "pipe" method?
Promise Pipelines in JavaScript

You're using .then in a way it's not supposed to be used--you're arguing a Deferred to it, when all that .then expects is a plain function to be added as a callback.
The .then method returns the original Deferred, which has been resolved already. When the Deferred resolves, all callbacks added with .then are executed immediately.
On the other hand, the .pipe function takes either a set of functions, or a Promise (which is what you're sending it) and resolves based on the status of the original Deferred. The functionality of .pipe is actually what you're looking for!

Related

A Worker running in the background for knockoutjs vewmodel

I have a viewmodel for a knockoutjs component. In the viewmodel, there is a function init() that executes for several minutes. Because of this, the UI of the component on the browser freezes until the init() finishes its execution.
function myViewModel(){
self = this;
self.x = ko.observable(0);
self.y = ko.observableArray([]);
self.z = ko.observable({});
self.init = function(){
//Need to use JQuery here
//loading stuff from DB via JQuery ajax
//assign retrieved data to x and y and z
}
}
Is there a way to run init() in the background?
I looked at the possibility of Worker, which runs in the background, but Worker needs to use JQuery. If I pass JQuery (and several other JSON objects) to worker via postMessage, like this: worker.postMessage($), then I get the error:
Failed to execute 'postMessage' on 'Worker': An object could not be cloned.
Any idea how to make init() run in the background to avoid frozen UI?
I tried timeout, like below, but UI still freezes:
self.executeAsync = function(func) {
setTimeout(func, 0);
};
self.executeAsync(self.init);
You can use promises.
As for the comments, your problem is that you know how to use all the ajax promises, but you don't know how to implement your own promise.
As you're using jQuery, let's do it with this library. It's only 3 steps
create a deferred object like this: var deferred = $.Deferred();
return a promise from this deferred, so that you can use .then to include the callbacks when the promise is resolve or rejected. For example: return deferred.promise(); or, if you're returning and object, you can return the promise as a member of that object, to check for completion: return { ..., promise = deferred.promise()};
When your code succeeds or fails, resolve or reject your promise, like this: deferred.resolve(); or deferred.reject();
In your case, create a deferred at the beginning of init(), and when all the jQuery ajax are completed, resolve the promise, or reject it, if something goes wrong.
Some notes on Deferred functionality:
If you resolve (or reject) your deferred passing an object, you'll receive that object as a parameter for your callback
You can return an object wrapped in a promise. For example, if your init() returns an object (for example an object implementing an API), you can return that object wrapped in a promise like this: return deferred.promise(retVal);
The implementation of jQuery promises is not Promises/A+ compatible until version 3.0. That has to do with chaining and exception propagation in callback chains. It will not affect you for this case. In the meantime, you can use other promises libraries like Q, or rsvp, which are Promises/A+ compatible, and implement more functionalities.
You can use deferred.notify to signal progress. Then you can specify a progress callback
Documentation for jQuery's Deferred object.
Pseudo code:
init: function() {
var deferred = $.Deferred();
// run your stuff, for example with setTimeout() or setInterval()
// so that the code follows running on the same line
// eventually, your stuff will resolve or reject the deferred/promise
return deferrer.promise();
}
Then you can use callbacks with your init, like usual: init().then(...);
As you don't explain what your init function does, it's not clear if you have to update some observables, or show some controls or whatever when the promise is resolved. That's up to you.
NOTE: I'm afraid you can be doing something wrong if you manipulate DOM objects with jQuery: when using knockout, all the DOM manipulation should be solved with ko bindings. Mixing up both ways of DOM manipulation can soon become cumbersome
You can import jQuery to the Worker script with importScripts().
At the top of your Worker script, just put importScripts('path/to/jQuery') and you'll have access to jQuery. The importScripts function is a global function in the WorkerGlobalScope interface, so all your workers should have access to it.

JavaScript native Promise execute callback on both results

Is there any way to execute callback on both results of Promise object?
For example I want to make some cleanup logic after execution of xhr request. So I need to do something like this:
var cleanUp = function() { something.here(); }
myLib.makeXhr().then(cleanUp,cleanUp);
In jquery Defered for example i can use method always():
myLib.makeXhr().always(function() { something.here(); });
Does Promise support something like this?
No, there is none. It was discussed but the spec is minimal. It doesn't include a bunch of other functionality. It's designed to interoperate well with library promises and to provide simple functionality.
Here is a correct polyfill of that proposal originally made by StefPanner.
Moreover, I disagree with the current now deleted answers adding it themselves because they're all doing it wrong (as an enumerable property - no fun). Even if we ignore what it does to the return values and error state of the returned promise. The intended way to extend native promises is by subclassing them, sadly, no browsers support this yet so we'll have to wait.
Instead of messing with native prototypes, we should use a different pattern:
openDb().then(foo).then(bar).finally(close).then(more);
Is susceptible to us forgetting to call close, even if we open it 100 times in our app, forgetting to close it even once can still be devastating. On the other hand - we can use the disposer pattern which some promise libraries provide built in ourselves:
openDb(function(db){
return foo(db).then(bar);// chain here
}).then(more);
Basically - this pattern means instead of having openDB return a promise - we have it take a function and return a promise, when the function is run, if it returns a promise we wait for that promise to resolve. It looks something like:
function openDb(withDb){
return justOpenWithoutCleanUp().
then(withDb).
then(clean, function(e){ clean(); throw e; }); // note the rethrow
}
Promise object supports 'always'.
For eg:
var oPromise= jQuery.ajax({
url:YOUR_URL
}).always(function(){
YOUR_CLEAN_UP_METHOD();
})

when.all() 2.2.1 in cujojs doesn't reject if one deferred rejected

I am having trouble working out why a promise is being fulfilled when the documentation says it should be rejected.
Open http://jsbin.com/orifok/5/edit and click the go button, and it alerts "ok" when it should alert "fail".
Replace when221.all... with deferred2.promise.then(stepFulfilled, stepRejected); and the rejection occurs.
I did make a modification to the when.js code to make the when221 variable global, rather than needing a dependency upon the require.js library (see http://pastebin.com/J8wCqjWM compared with original https://github.com/cujojs/when/blob/2.2.1/when.js).
when.all() documentatation says: If any of the promises is rejected, the returned promise will be rejected with the rejection reason of the first promise that was rejected - see https://github.com/cujojs/when/blob/master/docs/api.md#whenall
tl;dr: when.all must be passed a promise, not a deferred, and the programmer needs to be very careful to not make that mistake.
Brian Cavalier answered this here: https://github.com/cujojs/when/issues/172 and the modified code is here: http://jsbin.com/orifok/10/edit
The answer is as follows:
Hi, your example code passes deferred objects to when.all. Deferred objects are not promises. Unfortunately, some libraries, such as jQuery, and admittedly, earlier versions of when.js, conflate promises and deferred objects. To try to clarify, I've recently started referring to deferred objects as simply the pair {resolver, promise}. You should pass promises, not deferred objects, to when.all.
Here's a revised version of your example that will work as you expect:
(function(){
var deferred1 = when221.defer();
var deferred2 = when221.defer();
window.clickgo = function() {
// Pass the promise, not the deferred
when221.all([deferred1.promise, deferred2.promise]).then(stepFulfilled, stepRejected);
deferred2.reject('foooo');
};
function stepFulfilled() {
alert('ok');
}
function stepRejected(failed) {
alert('failed ' + failed);
}
})();
Note also that when,js >= 2.2.0 also has a new, lighter weight promise creation API when.promise, which I've started recommending over when.defer. They are suited to different situations, but I've found that I prefer it most of the time.
Hope that helps!

Unable to reject jquery deferred with a piped function if it was resolved by a previous pipe

The code is pretty straight foreword:
var handleNextPressedDeferred = $.Deferred();
$('input:button').live('click',function(){
console.log('resolving');
return handleNextPressedDeferred.resolve();
});
handleNextPressedDeferred.pipe(function(){
console.log('rejecting');
return $.Deferred().reject();
});
var handleNextPressedPromise = handleNextPressedDeferred.promise();
handleNextPressedPromise.done(function(){
console.log('done');
});
handleNextPressedPromise.then(function(){
console.log('then');
});
handleNextPressedPromise.fail(function(){
console.log('fail');
});
After the original button click resolves the deferred, I'm interested in rejecting it by the piped function.
The expected outcome when the button is clicked is:
resolving
rejecting
fail
The actual outcome when the button is clicked is:
resolving
rejecting
done
then
What am I not understanding correctly here? I've tried a million variations of this and couldn't get it to work as expected.
.pipe creates a new Deferred. In your code you are "connecting" .done, .then and .fail to the previous Deferred which is not rejected, that's why .done and .then are executed instead of .fail.
Try to replace this :
handleNextPressedDeferred.pipe(function(){
console.log('rejecting');
return $.Deferred().reject();
});
var handleNextPressedPromise = handleNextPressedDeferred.promise();
by
var handleNextPressedPromise = handleNextPressedDeferred.pipe(function(){
console.log('rejecting');
return $.Deferred().reject();
}).promise();
and it will work.
Fiddle here
Edit:
I see you are using both then and pipe in your code. I don't know which version of jQuery you are using, but be careful that since jQuery 1.8, pipe and then are strictly equivalent and return both a new Deferred. then is no longer syntactic sugar for .done and .fail.
See jQuery deferreds and promises - .then() vs .done() and pipe() and then() documentation vs reality in jQuery 1.8 for more information.

What's the difference between a Deferred object and its own promise object?

Let's create a simple Deferred object:
defer = $.Deferred( function ( defer ) {
setTimeout( defer.resolve, 3000 );
});
The above Deferred object will be in the "pending" state for 3 seconds, and then switch to the "resolved" state (at which point all the callbacks bound to it will be invoked).
Let's also retrieve the promise of that Deferred object:
promise = defer.promise();
Now, to add callbacks which are going to be invoked once the Deferred object is resolved, we can use .done() or .then(). However, we can invoke this method both on the Deferred object itself or its own promise object.
defer.then( handler );
or
promise.then( handler );
In both cases, the handler function will be invoked (after 3 seconds in this case).
If we use $.when, we can again pass the Deferred object itself or its promise object:
$.when( defer ).then( handler );
or
$.when( promise ).then( handler );
Again, there is no difference between the above two lines of code.
Live demo: http://jsfiddle.net/G6Ad6/
So, my question is since we can invoke .then(), .done(), etc. on the Deferred object itself and since we can pass that Deferred object into $.when(), what's the point of .promise() and retrieving the promise object? What's the purpose of the promise object? Why is there this redundancy in functionality?
It creates a "sealed" copy of the deferred value, without the .resolve() and .reject() methods. From the documentation:
The deferred.promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request.
It's used when it doesn't make sense for the value to be modified. For example, when jQuery makes an AJAX request it returns a promise object. Internally it .resolve()s a value for the original Deferred object, which the user observes with the promise.
When using the "promise" of a Deferred object the observers (objects waiting for resolve for exemple) dont have direct access to the Deferred object itself, so they can't call, for exemple, the method "Resolve" of that Deferred. It is a way of protecting the original Deferred.
With Deferred, you can control its state set.
When it comes to the Promise, you can read state and maybe attach callback. get

Categories