I'm trying to understand Promise. But here I'm confused.
I want to create a test function that will print 3000 after 3 second, then print 2000 after 2 second, then print 1000 after 1 second. Here is my code:
'use strict';
var Q = require('q');
function delayConsole(timeOut) {
var defer = Q.defer();
setTimeout(function(){
console.log(timeOut);
defer.resolve(2000);
},timeOut);
return defer.promise;
}
// This works
delayConsole(3000).then(function(){
return delayConsole(2000);
}).then(function(){
return delayConsole(1000);
});
// This doesn't work. Why?
delayConsole(3000).then(delayConsole(2000)).then(delayConsole(1000));
There, you call the function delayConsole immediately :
.then(delayConsole(2000))
That is : you don't pass the function but the result of the function call, you don't wait for the promises to be chained.
When you do
then(function(){
return delayConsole(2000);
})
then you pass a function, not the result of that function call. The function can be called when the previous element in the promise chain is solved.
I just thought I'd share that you can make this construction work which is sometimes easier to use:
promise.then(delayConsole(3000)).then(delayConsole(2000)).then(delayConsole(1000));
by changing delayConsole() to this:
function delayConsole(timeOut) {
return function() {
var defer = Q.defer();
setTimeout(function(){
console.log(timeOut);
defer.resolve(2000);
},timeOut);
return defer.promise;
}
}
This way, calling delayConsole() just captures the timeout argument and returns a function that can be called later by the promise .then handler. So, you are still passing a function reference to the .then() handler which lets the promise engine call the internal function sometime later rather than execute it now.
Related
I'm a bit new to JavaScript and i can't figure out why the printing order of the following code is Noam and then Amit and only them.
can someone clarify it?
function rc(){
console.log('Assaf');
}
function thenCall(){
console.log('Amit');
}
function myPromise(){
return Promise.resolve(function(){
console.log('Yarden');
rc();
});
}
myPromise().then(function(){thenCall()});
console.log('Noam');
Promise.resolve takes a result which is passed to the then. It does not call the function. Notice the callback to .then.
function rc(){
console.log('Assaf');
}
function thenCall(){
console.log('Amit');
}
function myPromise(){
return Promise.resolve(function(){
console.log('Yarden');
rc();
});
}
myPromise().then(function(fn){
console.log(fn); // notice
thenCall()
});
console.log('Noam');
The function inside Promise.resolve should be executed as it will wait for a result to return:
function rc(){
console.log('Assaf');
}
function thenCall(){
console.log('Amit');
}
function myPromise(){
return Promise.resolve(function(){
console.log('Yarden');
rc();
}());
}
myPromise().then(function(){thenCall()});
console.log('Noam');
Let's divide the question by the people you're trying to log
Noam
Noam is printed first, because there is no async process in the code. You do use a Promise but it instantly executes the resolve method.
Amit
When Promise.resolve gets invoked, the function in the .then method will be executed. Therefore Amit gets printed in the console correctly.
Yarden and Assaf
Promise.resolve immediately resolves a new Promise and calls therefore the .then method immediately. The argument given to Promise.resolve is the value that will be passed as an argument to the .then method.
To create an actual Promise that either resolves or rejects based on some logic, this should get you started:
var a = true;
function myPromise() {
return new Promise(function (resolve, reject) {
console.log('first');
if (a) {
return setTimeout(function () {
resolve('value_to_pass_to_then');
}, 1000);
}
return reject('value_to_pass_to_catch');
});
}
myPromise()
.then(function (val) { console.log('second'); })
.catch(function (val) {})
The Yarden function is what the promise returned by myPromise() resolves to. But you aren't doing anything with the resolve value:
myPromise().then(function(/* resolve value would go here */){thenCall()});
The value a promise resolves to is the first argument of the function passed to .then. But you don't have an argument there. If you wanted to run that function, you would have to use it as an argument and then call it explicitly (which would in turn call the function that prints Assaf):
function rc() {
console.log('Assaf');
}
function thenCall() {
console.log('Amit');
}
function myPromise() {
return Promise.resolve(function() {
console.log('Yarden');
rc();
});
}
myPromise().then(function(yardenFn) {
thenCall();
yardenFn();
});
console.log('Noam');
I have a test that requires one promise to be run and later in its then handler another promise returning function is run.
The first promise resolves, and a successful call is made to the next function which returns a promise. However, the then handler for that second promise never fires.
Is there another way to test nested promises using Jasmine 2.0?
Example problem:
describe("nested promise suite", function () {
var prom1 = function () {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve('prom1');
}, 500)
return deferred.promise;
};
var prom2 = function () {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve('prom2');
}, 500);
return deferred.promise;
};
iit("nested promise test", function (done) {
prom1()
.then(function (result) {
console.log('prom1 result ', result);
prom2()
.then(function (result2) {
console.log('prom2 result ', result2);
})
.finally(function() {
console.log('you did it');
})
})
.finally(done); //this causes promise one to resolve properly but unsure of how to make the second promise resolve properly
$timeout.flush();
})
});
I'm not sure if this is the problem in your original code as well, but in this example, your second console.log doesn't fire because prom2 adds a new timeout after the first has been flushed. The promise from prom2 then waits for this new timeout to flush, which never happens:
prom1() is called
The first promise and timeout are created
The first timeout is flushed, resolving the first promise
The then() block is triggered, calling prom2()
The second promise and timeout are created
done() is called.
You can try adding a second $timeout.flush(); call right after the one that's already there, which will flush the second timeout, resolve the second promise, and log the missing messages.
I want function A to finish execution and only after that function B should start executing. When I call function A and then function B, it seems both are executing simultaneously. And after function B completes, I want to call a third function update_dropdown().
My code looks like this:
function A {
for (var i = 0; i < 5; i++) {
var promise = $.get(url+i);
$.when(promise).then(function () {
$.post(url);
});
}
}
function B {
var x = $.get(url);
var promise = $.post(url+x);
$.when(promise0).then(function () {
update_dropdown();
});
}
Please can you tell me how I can make these 3 function calls happen sequentially.
OK, it's getting a little bit clearer what you actually want (based on your recent comments to address clarifying questions) though there are still at least two options open.
For an operation like this, you probably want to take advantage of a number of promise features:
jQuery's Ajax calls already return a promise so you can just use those directly
To serialize operations, you can just chain multiple promise operations together
To make async operations serialize properly, you can return a promise from a .then() handler and the master promise will resolve only when all the chained promises have resolved (kind of a built-in $.when() without having to explicitly call $.when()).
You can chain as many operations together as you want and the master promise will tell you when they are all done.
If you return promises from both A() and B(), then the callers of those functions can monitor when they are done with promise methods which then lets you chain A().then(B) to sequence those two.
When you sequence operations with chaining, the prior methods resolve data is passed to the next .then() handler function in the chain as the first argument to the .then() handler function so if you need the prior data for the next operation, it is right there to use.
So, with all those capabilities, it's just a matter of putting the right scaffolding around the code to implement the exact sequencing you want. Here are two different options:
Option 1: If you want to serialize everything in A() so that all 10 requests happen in serial fashion (the next one proceeds only when the prior one is done), then it could look like this:
// serialize all requests
function A() {
var p = $.get(url).then(function(data) {return $.post(url)});
for (var i = 1; i < 5; i++) {
// chain four more pairs of requests onto the original promise
p = p.then(function() {return $.get(url)})
.then(function(data) {return $.post(url)});
}
// return the promise so callers can monitor when A() is done
return p;
}
function B() {
// sequence these three operations one after the other
return ($.get(url)
.then(function(data) {return $.post(url + x)})
.then(update_dropdown)
);
}
// run them both, one after the other
A().then(B);
Option 2: If you want the 5 pairs of requests in A() to run in parallel, with only the last part of A() waiting until the 5 pairs of requests are done, then it could look like this:
// parallelize pairs of requests
function A() {
var promises = [];
for (var i = 0; i < 5; i++) {
// execute 5 pairs of requests where each pair is serialized in itself
promises.push($.get(url).then(function(data) {return $.post(url)}));
}
// return a promise that resolves only when all the other promises are done
return $.when.apply($, promises);
}
function B() {
// sequence these three operations one after the other
return ($.get(url)
.then(function(data) {return $.post(url + x)})
.then(update_dropdown)
);
}
// run them both, one after the other
A().then(B);
These use the concept that if you return a promise from a .then() handler function, then it will chain multiple async operations together and the master promise is only resolved when all the chained operations are resolved. This is very powerful for sequencing multiple ajax operations and you can even do it for operations in a loop like you have.
Something like this should work
function A {
var xhr = [];
for (var i = 0; i < 5; i++) {
xhr.push( $.get(url) );
}
$.when.apply($, xhr).then(B);
}
function B {
$.get(url).done(function(x) {
$.post(url + x).done(update_dropdown);
});
}
Note the use of an array to keep the promises in, then using $.when with apply() to fire a callback when all the ajax requests in the loop has finished.
Assumptions assumptions ...
Let's assume that :
the url for every get is the same as that for its corresponding post
the urls for each get-post pair should vary
the five get-post pairs in A can occur in parallel and we are not interested in the returned data
First, a utility function :
function getThenPost(url, appendToURL) {
return $.get(url).then(function(x) {
return (appendToURL) ? $.post(url + x) : $.post(url);
});
}
then A and B, both of which call the utility :
function A(urls) {
return $.when.apply(null, urls.map(function(url) {
return getThenPost(url, false);
}));
}
function B(url) {
return getThenPost(url, true);
}
and finally an expression that calls A and B :
A(['/path/0', '/path/1', '/path/2', '/path/3', '/path/4']).then(function() {
B('/path/5');
}).then(update_dropdown);
It should be reasonably simple to adjust this code if assumptions 1 and 2 are incorrect.
If assumption 3 is incorrect then A will require more extensive modification.
We can call our choice function in our way using jquery Deferred Object.
It is very simple let see successfully run example:
<body>
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript">
// I want to call function in order of f1,f2,f3,f4 every time when i will execute this html page.
promise = f1().then(f2).then(f3).then(f4); // Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
function f1() {
var d = $.Deferred();
setTimeout(function() {
// our code here....
alert("1");
console.log("1");
d.resolve(); // resolve() :Resolve a Deferred object and call any doneCallbacks with the given args.
},1000); // You set some time for each method.
return d.promise(); //promise(): Return a Deferred’s Promise object.
}
function f2() {
var d = $.Deferred();
setTimeout(function() {
alert("2");
console.log("2");
d.resolve();
},1000);
return d.promise();
}
function f4() {
var d = $.Deferred();
setTimeout(function() {
alert("4");
console.log("4");
d.resolve();
},1000);
return d.promise();
}
function f3() {
var d = $.Deferred();
setTimeout(function() {
alert("3");
console.log("3");
d.resolve();
},1000);
return d.promise();
}
</script>
Javascript without extra work is single threaded. that means functions are not able to be executed simultaneously. but the problem is that the $.get() and $.post() calls are asynchronous. that means they are executed whenever the requested data arrives your client. (first come first serve)
an solution would be to execute function B after all the results ob A arrived, or to hold back all results and handle all data at once then run update_dropdown().
I have the following durendal code:
var variable = ko.observable("");
function activate(){
return myModel.doSomething(variable);
}
doSomething is defined as follow:
function doSomething(variable){
if(someCondition)
return $.ajax(...).success(function(data){variable(data)});
variable(defaultValue);
return defaultValue;
}
This code works fine when someCondition==true, variable gets initialized with the ajax call results.
when someCondition==false the activate method returns without the variable being initialized.
From what I read in the documentation, the happy path works because the ajax call returns a promise but not in the else case.
I tried this:
function activate(){
return myModel.doSomething(variable).promise();
}
but I get an error saying that the observable does not have a promise method which makes sense.
Question
How can I make the else clause return a promise just as what the ajax call does?
You should better encapsulate the logic inside the doSomething function, so it always returns a promise. You can use the system.defer from Durandal to achieve this:
var system = require('durandal/system');
function doSomething(){
return system.defer(function(dfd){
if (someCondition)
return $.ajax(...).then(function (res) {
dfd.resolve(res);
});
else
dfd.resolve(defaultValue);
}).promise();
}
And then in your activate:
function activate(){
return myModel.doSomething().then(function(res){
variable(res);
});
}
Notice that the variable to be updated is no loger passed to the function. The promise will always return a value, either from the AJAX call or the default one.
A quick question on how to use Jquery.deferred to make a slow synchronous function return a promise instead.
What I've done so far is this :
function sayIt(ms) {
setTimeout( function() { console.log('what I say'); }, ms);
}
function doIt() {
return $.Deferred( function() { sayIt(2000); }).promise();
}
doIt().then( function() { console.log('ah'); });
the sayIt(2000) always goes through but the chained function after the 'then' never fires.
If I do this :
doIt().then( console.log('ah'));
the 'ah' comes up right away, and then the 'what I say' 2000ms later - what I want is of course the opposite - that after two seconds I get 'what I say' and then 'ah' right after.
Any suggestions appreciated!
To do something synchronously, but still use a promise, do:
function slowPromise() {
var def = $.Deferred();
// do something slow and synchronous
...
// resolve the deferred with the result of the slow process
def.resolve(res);
// and return the deferred
return def.promise();
}
The effect is that you still get a promise, but that promise is already resolved, so any .then() which is subsequently registered on it proceeds immediately.
The advantage of this pattern is that if you subsequently replace the synchronous code with something asynchronous the function still has the same external interface.
If you want to execute a function after the timeout has expired, you need to call resolve() on the Deferred object from within the timeout expiration function:
function sayIt(ms) {
var d = $.Deferred();
setTimeout(function() {
console.log('what I say');
d.resolve()
}, ms);
return d;
}
In this way you constrain the resolution of the Deferred object to the expiration of the timeout.
To achieve what I believe is your intent:
function doIt() {
return sayIt(2000).promise()
}
The .promise() call is optional. It only restricts the available interface for callers: by returning a Promise instead of the original Deferred object, the caller can only react to events, but not triggering them. Reference: http://api.jquery.com/deferred.promise/.
Eventually, your original call:
doIt().then( function() { console.log('ah'); });
Will output:
// 2 seconds delay
what I say
ah