I thought I had a decent understanding of promises, until I ran into a problem with a simplifed code snippet bellow. I was under the impression that the console.log calls would output first second third, but instead results in second third first.
Can someone explain why the second and third promises are able to continue on without waiting for the first.
var Q = require('q');
(function() {
var Obj = function() {
function first() {
var deferred = Q.defer();
setTimeout(function() {
console.log('in the first')
deferred.resolve();
}, 200);
return deferred.promise;
}
function second() {
return Q.fcall(function() {
console.log('in the second');
})
}
function third() {
return Q.fcall(function() {
console.log('in the third');
})
}
return {
first: first,
second: second,
third: third
}
};
var obj = Obj();
obj.first()
.then(obj.second())
.then(obj.third());
}());
You shouldn't be invoking the function, but pass the function, like this
obj.first()
.then(obj.second)
.then(obj.third);
Output
in the first
in the second
in the third
Related
I have this function:
waitForFreeAccnt.prototype.isMemberFree = function () {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return true;
} else {
console.log('it is not free');
return false;
}
});
};
I would like to wait till the account is free for up to 10 seconds with something like that:
var test = function () {
for (var start = 1; start < 10; start++) {
var result = self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
But it doesn't work because self.api.getMemberInfo is asynch call. This is super frustrating with Javascript. Any other language it would be so simple to do. How do I force the for loop to wait for self.isMemberFree() to finish executing before proceeding with the loop?
Also to note, this is not in browser execution so I don't care about anything hanging.
When dealing with asynchronous code, you need to make use of callbacks. That is, if you want to do a() and b() in order but a() does something asynchronously, then you need to call b() from within a() once a() has a result. So not:
a(); // does something asynchronously
b(); // tries to use a()'s result but it isn't available yet
... but rather
a(b); // pass b to a() and a() will call it when ready
function a(callback) {
triggerAsyncFunction(function(result) {
if (result === something)
callback("a just finished");
});
}
Note that a() doesn't refer to b() by name, it just calls whatever function is passed in as an argument.
So applying that to your code, maybe something like this:
waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
var self = this;
self.api.getMemberInfo(function () {
cbf(self.api.connect.accountType === 'FREE');
});
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
var attempts = 0;
var self = this;
(function attempt() {
self.isMemberFree(function(free) {
if (free)
callback(true);
else if (++attempts < maxAttempts)
setTimeout(attempt, 1000);
else
callback(false);
});
)();
};
this.testMemberXTimes(10, function(isFree) {
// the next part of your code here, or called from here
// because at this point we know we've tested up to
// ten times and isFree tells us the result
});
Note that the way I coded getMemberInfo() it is basically doing the same thing yours was, but instead of returning a boolean it is calling the callback function and passing the same boolean value that you were returning. (I've removed the console.log()s to make the code shorter.)
Note also that you could structure the above to use promises, but the end result will be the same.
You could return a Promise
waitForFreeAccnt.prototype.isMemberFree = function () {
return new Promise((reject, resolve)=>
// set a timeout if api call takes too long
var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
// make api call
this.api.getMemberInfo(()=> {
clearTimeout(timeout);
resolve(this.api.connect.accountType === 'FREE');
});
);
};
Then use it like this
whatever.isMemberFree().then(isFree=> {
if (isFree)
console.log('it is free');
else
console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
console.log(err.message);
});
Building on naomik's answer, if you do it that way you can pretty easily use a for loop with it, using the (most likely) upcoming async/await feature - though it's not part of ES2015.
// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
for (var start = 1; start < 10; start++) {
// Right here we're using "await" - it makes JavaScript *wait* for
// the promise that comes from self.isMemberFree() to be finished.
// It's really handy because you can use it in loops like "for" and
// "while" without changing the flow of your program!
var result = await self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
For now you'll need to use a transpiler like Babel or Traceur before you can really use async/await, though. It's only supported in Microsoft Edge 14 right now.
And a big emphasis that what is returned from test() isn't whatever you directly return from inside it. If I do this:
var test = async function() { return 15; };
var result = test();
I'm not going to get 15 - I'll get a promise that will resolve as 15:
result.then(function(res) {
console.log(res); // 15
});
// or, use an async function again:
var main = async function() {
console.log(await res); // 15
};
main();
I don't have my work laptop today because it is Sunday, I'm coding this on sublime. Apologise if the syntax is a bit off.
To solve your problem I would recommend changing isMemberFree() to take in a callback function. This is because isMemberFree is async, and you will need a way to report the result after it has done the work.
Then change test function to use setTimeout API to wait a second.
Wrap the function call for isMemberFree() to be in a nested function and call it recursively, that way you'll have synchronize control over the async calls.
Look at the coding example:
waitForFreeAccnt.prototype.isMemberFree = function (done) {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return done(null, true);
} else {
console.log('it is not free');
return done(null, false);
}
});
};
var test = function () {
var testMembership = function(waitAttempt, isFree) {
if (isFree) {
return;
}
else if (waitAttempt > 10) {
// wait exceeded, do something.
return;
}
setTimeout(function() {
self.isMemberFree(function(err, isFree) {
testMembership(waitAttempt+=1, isFree);
});
}, /*total milliseconds in 1sec=*/1000);
}
testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};
What the above code does is that, presumably something has already been done to the member's account and now test function is called. So it waits for 1 second, then call isMemberFree function, this happens recursively until either isMemberFree() returns true OR the 10 seconds wait has been exceeded.
I have a for_users function that gets an array of users from a web service, executes a passed function f on the received array, then calls a continuation f_then callback.
// Execute f on every user, then f_then.
function for_users(f, f_then)
{
// Get all users from the database, in user_array
db.get_all_users(function(user_array)
{
// Execute f on every user
user_array.forEach(f);
// Call continuation callback
f_then();
});
}
When calling for_users, passing an asynchronous function as the f parameter, I would like all the f callbacks to end before calling f_then. This is obviously not happening in the current code, as user_array.forEach(f) does not wait for f to finish before starting the next iteration.
Here's an example of a problematic situation:
function example_usage()
{
var temp_credentials = [];
for_users(function(user)
{
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential)
{
// ...
});
}, function()
{
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
}
How can I implement for_users such that if f is an asynchronous function, f_then is called only when all f functions are completed?
Sometimes, though, the passed f to for_users is not asynchronous and the above implementation could suffice. Is there a way to write a generic for_users implementation that would work as intended both for asynchronous and synchronous f functions?
this should work for you:-
function for_users(f, f_then) {
db.get_all_users(function(user_array) {
var promises = [];
user_array.forEach(function(user) {
promises.push(new Promise(function(resolve, reject) {
f(user);
resolve();
}));
});
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises);
}
});
}
Simple Test below:-
function for_users(f, f_then) {
var users = [{ID: 1}, {ID: 2}, {ID: 3}];
var promises = [];
users.forEach(function(user) {
var promise = new Promise(function(resolve, reject) {
f(user);
resolve();
});
promises.push(promise);
})
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises)
}
for_users(function(user) {
console.log(user.ID);
}, function() {
console.log('finshed')
})
You can add next continuation callback to f function like this:
function for_users(f, f_then) {
// Get all users from the database, in user_array
db.get_all_users(function(user_array) {
// Execute f on every user
(function recur(next) {
var user = user_array.shift();
if (user) {
f(user, function() {
recur(next);
});
} else {
// Call continuation callback
next();
}
})(f_then);
});
}
and then you will be able to call this function using this:
for_users(function(user, next) {
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential) {
next();
});
}, function() {
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
var getCredentials = function(step){
return function(user){
database.get_credentials(user.ID, function(credential) {
step(credential);
});
};
};
var allFinish = function(f){
return function(step) {
return function(arr){
var finished = 0;
var values = new Array(arr.length);
if(arr.length){
arr.forEach(function(el, i){
f(function(value){
if(finished === arr.length){
step(values);
} else {
values[i] = value;
finished++;
}
})(el);
});
} else {
step(values);
}
};
};
};
var forEachUser = function(doSomething){
db.get_all_users(allFinish(getCredentials)(doSomething));
}
And then you can just simply do:
forEachUser(function(tempCredentials){
//tempCredentials === allCredentials
});
There's probably better ways to handle the order of values inserted in the array in allFinish. allFinish works by taking a function that takes a step and calling it with a step function that will call another step function when all calls are finished. I curried the functions, but it isn't really necessary. It's just a convenience.
I'm using ES6 javascript promises in Chrome and am having trouble understanding why the promise executed within the function _getStatus() is not returning the result argument in the success handler which would result in the alert box containing "done". Instead, I get an alert box that says "undefined".
myNameSpace = function(){
var obj = {
groupA: {
status: "inactive"
},
groupB: {
status: "active"
}
};
function _getStatus(group){
_finishTask().then(function(result){
return result; // doesn't return anything
});
}
function _finishTask(){
var promise = new Promise(function(resolve, reject){
// do some task before resolving
resolve("done");
});
return promise;
};
return{
getStatus:_getStatus
}
}();
$(function(){
alert(myNameSpace.getStatus("groupA")); // why is this "undefined" instead of "done"?
});
Because this is not how Promises work. You need to make sure both _getStatus and _finishTask return Promise objects. Then you will be able to use those promises API methods to execute subsequent code what promise is resolved.
So your code should look something like this:
myNameSpace = function() {
var obj = {
groupA: {
status: "inactive"
},
groupB: {
status: "active"
}
};
function _getStatus(group) {
return _finishTask().then(function(result) {
return result + " and tested";
});
}
function _finishTask() {
return new Promise(function(resolve, reject) {
// do some task before resolving
resolve("done");
});
};
return {
getStatus: _getStatus
}
}();
myNameSpace.getStatus("groupA").then(alert);
Finally, regarding this construction:
return _finishTask().then(function(result) {
return result;
});
_finishTask returns a Promise object, when this promise is resolved you get into then callback. Whatever value you return from this callback becomes a new resolution value for the subsequent success callbacks down the resolution chain.
Demo: http://plnkr.co/edit/K1SWKuTYA3e46RxdzkCe?p=preview
You cant return a result from an asynchronous function as the code running it has already finished by the time the response is returned.
You can however pass in a callback to execute once the code is complete:
function _getStatus(group, callback){
_finishTask().then(function(result){
callback(result);
});
}
$(function(){
myNameSpace.getStatus("groupA", function(result) {
alert(result);
});
});
or use the promise api itself (in this case your _getStatus method is a little redundant):
function _getStatus(group){
return _finishTask();
}
$(function(){
myNameSpace.getStatus("groupA").then(function(result) {
alert(result);
});
});
There are multiple questions that already have an answer about this, but all aren't working so far it this kind of setup.
function login(u,p) {
console.log(1);
return $.post(url, {u,p});
}
function out() {
console.log(3);
//a function that does not return deferred
// clear cookies
}
function doSomething() {
console.log(2);
// a function that returns a deferred
return $.post(...);
}
var data = [{u: 'au', p: 'ap'}, {u: 'bu', p: 'bp'}]
$.each(data, function(k,v){
login(v.u, v.p).then(doSomething).then(out);
});
I was expecting it's sequence to be like:
1
2
3
1
2
3
But I get
1
2
1
3
2
3
Why is it like that, even though I am waiting for the promise to be resolve using then
If you want the logins to run synchronously:
var p = new jQuery.Deferred();
$.each(data, function(k,v){
p.then(function() {
return login(v.u, v.p);
}).then(doSomething).then(out);
});
Each new item iterated over in $.each won't trigger a new response until p finishes the last one.
The idea is to create a recursive function like #Popnoodles have mentioned.
e.g.
function a() {
return $.post();
}
function b() {
return $.post();
}
function c() {
console.log('no promise.');
}
// and the recursive main function
function main() {
if(counter < data.length){
$.when(a().then(b).then(c)).done(function(){
counter++;
main();
});
}
}
main();
Here is how it works, open the console to see how it logs the function in sequence.
I've been trying to wrap my head around asynchronous programming and the use of promises. To help understand them, I've written some trivial nested code, but have run into a snag.
Here's the code: http://pastebin.com/hBtk9vER
Make sure you install when library (npm install)
var when = require('when');
function promise() {
console.log("Promise");
promiseRead().then(function(string) {
console.log("Inner promise");
console.log(string);
});
}
function promiseRead() {
console.log("PromiseRead");
return baz().then(function() {
console.log("Inner Read");
var deferred = when.defer();
setTimeout(function() {
deferred.resolve("Hello World");
}, 5000);
});
}
function baz() {
console.log("BAZ");
return when(true);
}
promise();
My problem is that console.log(string) is undefined, when I was expecting it to be "Hello World" after promiseRead() resolves. Interestingly, when I remove the timeout, it works as expected. Can anybody help explain this, why the promise function is executing it's code before promiseRead() has finished timeout.
Much appreciated
It looks like you need to return your deferred object in promiseRead()
some updates
var when = require('when');
function promise() {
console.log("Promise");
promiseRead().then(function(string) {
console.log("Inner promise");
console.log(string);
});
}
function promiseRead() {
console.log("PromiseRead");
return baz().then(function() {
console.log("Inner Read");
var deferred = when.defer();
setTimeout(function() {
deferred.resolve("Hello World");
}, 5000);
return deferred.promise;
});
}
function baz() {
console.log("BAZ");
return when(true);
}
promise();