I'm using jasmine-node 1.14.5, which underneath uses jasmine 1.3, and I'm having issues getting runs/waitFor to work properly with Promises.
In certain tests, I'd like to runs/waitFor to wait for a particular condition to happen, and when it occurs, fulfil a Promise that I return back. However, the moment I attempt to construct a Promise passing in the function(success, fail) parameter, none of the code inside the runs/waitFor gets called. However, if the Promise is resolved directly, it works. Any idea the former option does not work?
To give some examples, the following works fine:
it("should support async execution of test preparation and expectations", function(done) {
var p = Promise.resolve("boo")
.then(function() {
var p2 = Promise.resolve("whatever");
runs(function() {
flag = false;
value = 0;
intId = setInterval(function() {
console.log(value);
if (++value == 3) { clearInterval(intId); flag = true; }
}, 500);
});
waitsFor(function() {
return flag;
}, "The Value should be incremented", 5000);
runs(function() {
expect(value).toEqual(3);
});
return p2;
});
p.then(function() {
done();
}).catch(function(err) {
done(err);
});
});
But on the other hand, this does not work because although runs/waitsFor are called without problems, the callbacks inside do not:
it("should support async execution of test preparation and expectations", function(done) {
var p = Promise.resolve("boo")
.then(function() {
return new Promise(function (fulfil, reject) {
runs(function () {
flag = false;
value = 0;
intId = setInterval(function () {
console.log(value);
if (++value == 3) {
clearInterval(intId);
flag = true;
}
}, 500);
});
waitsFor(function () {
return flag;
}, "The Value should be incremented", 5000);
runs(function () {
expect(value).toEqual(3);
fulfil();
});
});
});
p.then(function() {
done();
}).catch(function(err) {
done(err);
});
});
I've also tried the following in the off chance but does not work either, it behaves the same way as the previous example:
it("should support async execution of test preparation and expectations", function(done) {
var p = Promise.resolve("boo")
.then(function() {
var outerFulfil;
var outerReject;
var p2 = new Promise(function(fulfil, reject) {
outerFulfil = fulfil;
outerReject = reject;
});
runs(function() {
flag = false;
value = 0;
intId = setInterval(function() {
console.log(value);
if (++value == 3) { clearInterval(intId); flag = true; }
}, 500);
});
waitsFor(function() {
return flag;
}, "The Value should be incremented", 5000);
runs(function() {
expect(value).toEqual(3);
outerFulfil();
});
return p2;
});
p.then(function() {
done();
}).catch(function(err) {
done(err);
});
});
Any idea how to solve it? Although the first example works, it does not behave the way I want because I only want the promise to be fulfilled once the assertions after the waitsFor have been carried out.
Cheers,
Galder
I ended up ditching runs/waitsFor altogether, and instead do a time based recursive Promise loop, e.g.
function waitUntil(expectF, cond, op) {
var now = new Date().getTime();
function done(actual) {
return cond(actual)
&& new Date().getTime() < now + MAX_WAIT;
}
function loop(promise) {
exports.sleepFor(100); // brief sleep
// Simple recursive loop until condition has been met
return promise
.then(function(response) {
return !done(response)
? loop(op())
: response;
})
.catch(function() {
return loop(op());
});
}
return loop(op())
.then(function(actual) {
expectF(actual);
});
}
op is an operation that returns a Promise.
For example, I wanted to wait until certain number of nodes have joined a cluster:
function waitUntilView(expectNumMembers, nodeName) {
return waitUntil(
function(members) { expect(members.length).toEqual(expectNumMembers); },
function(members) { return _.isEqual(expectNumMembers, members.length); },
getClusterMembers(nodeName)
);
}
Related
I have a function which calls dealCardSelectableAI(), which sets up a number of jQuery deferred promises. The function setCardName() is then called from within it. Once both functions complete their tasks saveGame() should then be triggered.
Everything works, except setCardName() does not complete before saveGame() is triggered. It appears that deferredQueue.push(setCardName(system, result)); is not operating as I expected. I am not sure where I'm going wrong or how to resolve the issue.
var setCardName = function (system, card) {
var deferred = $.Deferred();
require(["cards/" + card[0].id], function (data) {
var cardName = loc(data.summarize());
system.star.ai().cardName = ko.observable(cardName);
deferred.resolve();
});
return deferred.promise();
};
var dealCardSelectableAI = function (win, turnState) {
var deferred = $.Deferred();
// Avoid running twice after winning a fight
if (!win || turnState === "end") {
var deferredQueue = [];
_.forEach(model.galaxy.systems(), function (system, starIndex) {
if (
model.canSelect(starIndex) &&
system.star.ai() &&
system.star.ai().treasurePlanet !== true
) {
deferredQueue.push(
chooseCards({
inventory: inventory,
count: 1,
star: system.star,
galaxy: game.galaxy(),
addSlot: false,
}).then(function (result) {
deferredQueue.push(setCardName(system, result));
system.star.cardList(result);
})
);
}
});
$.when(deferredQueue).then(function () {
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
};
dealCardSelectableAI(false).then(saveGame(game, true));
Your code says call saveGame() and what is returned from the function call should be set to then. It is not saying, "call saveGame when done"
dealCardSelectableAI(false).then(function () { saveGame(game, true) });
I have been trying to understand the architecture behind a promise call in javscript and here is something that i assumed is happening behind the scene
function Promise() {
this.stack = [];
this.then = function(fn) {
this.stack.push(fn);
return this;
}
this.resolve = function(data) {
if (this.stack.length) {
var cb = this.stack[0];
this.stack.shift();
cb.call(null, {
resolve: this.resolve.bind(this)
}, data);
}
}
}
// --- below is a working implementation --- //
var bar = function() {
var promise = new Promise();
setTimeout(function() {
console.log("1");
promise.resolve();
}, 2000);
return promise;
}
bar().then(function(p) {
setTimeout(function() {
console.log("2");
p.resolve();
}, 1000);
}).then(function(p) {
setTimeout(function() {
console.log("3");
p.resolve();
}, 500);
}).then(function(p) {
setTimeout(function() {
console.log("4");
p.resolve();
}, 300);
});
in my library, after every resolve call, i am calling the next callback in the stack and shifting the array by one
however in other libraries that i have linked, every-time the first promise is resolved a loop is being run through out the entire array while it keeps calling the callback stacks.
D.js
function execCallbacks() {
/*jshint bitwise:false*/
if (status === 0) {
return;
}
var cbs = pendings,
i = 0,
l = cbs.length,
cbIndex = ~status ? 0 : 1,
cb;
pendings = [];
for (; i < l; i++) {
(cb = cbs[i][cbIndex]) && cb(value);
}
}
tiny Promise.js
_complete: function(which, arg) {
// switch over to sync then()
this.then = which === 'resolve' ?
function(resolve, reject) {
resolve && resolve(arg);
} :
function(resolve, reject) {
reject && reject(arg);
};
// disallow multiple calls to resolve or reject
this.resolve = this.reject =
function() {
throw new Error('Promise already completed.');
};
// complete all waiting (async) then()s
var aThen, i = 0;
while (aThen = this._thens[i++]) {
aThen[which] && aThen[which](arg);
}
delete this._thens;
}
tiny closure Promise.js
function complete(type, result) {
promise.then = type === 'reject' ?
function(resolve, reject) {
reject(result);
} :
function(resolve) {
resolve(result);
};
promise.resolve = promise.reject = function() {
throw new Error("Promise already completed");
};
var i = 0,
cb;
while (cb = callbacks[i++]) {
cb[type] && cb[type](result);
}
callbacks = null;
}
I want to understand, why is there a loop being run through the array to handle the next function chained to the resolve !
what am i missing in my architecture?
in my library, after every resolve call, i am calling the next callback in the stack and shifting the array by one
That's a callback queue, not a promise. A promise can be resolved only once. When making a .then() chain of promises, it simply creates multiple promise objects, each one representing the asynchronous result after a step.
You might want to have a look at the core features of promises.
I have a asynchronous function that needs to be called multiple times in the correct order. It's about uploading images to a server but like I said the images should be uploaded in the correct order.
My function looks like this:
function uploadImage(sku,fileName) {
console.log("Uploading image for sku="+sku+", imageName="+fileName);
var deferred = Q.defer();
var readStream = fs.createReadStream(rootPath+'/data/'+sku+"/"+fileName);
var req = request.post('http://localhost:3000/'+sku+'/upload/'+fileName);
readStream.pipe(req);
req.on('end', function() {
console.log("Image imageName="+fileName+" uploaded successfully.");
db.updateLastUploadedImage(sku,fileName).then(function (res) {
if(res) {
console.log("Table watches for sku="+sku+" updated.");
deferred.resolve(sku);
}
});
});
req.on('error',function(err) {
deferred.reject(err);
});
return deferred.promise;
}
I tried to bring it on with chaining the promises like documented in https://github.com/kriskowal/q but it's not working well. Somehow I do not come to the "then" block.
So I tried to make a recursive function but it also does not go inside the "then" block of the function call.
Only this method works but its not running through the correct order.
function uploadImages(sku) {
var promises = [];
for(var x=0; x<10; x++) {
promises.push(uploadImage(sku,(x+1)+".jpg")));
}
return Q.all(promises).then(function (res) {
return sku;
});
}
My recursive solution looks like this:
function uploadImages(sku,current,max) {
var deferred = Q.defer();
if(current<=max) {
uploadImage(sku,current+'.jpg').then(function (res) {
if(res) {
uploadImages(sku,current+1,max);
}
}, function (err) {
deferred.reject();
});
} else {
deferred.resolve(sku);
}
return deferred.promise;
}
What I'm looking for is something like this (but thats not the way to implement):
return uploadImage(sku,"1.jpg").then(function(res) {
return uploadImage(sku,"2.jpg").then(function(res) {
return uploadImage(sku,"3.jpg").then(function(res) {
return uploadImage(sku,"4.jpg").then(function(res) {
return uploadImage(sku,"5.jpg").then(function(res) {
return uploadImage(sku,"6.jpg").then(function(res) {
return uploadImage(sku,"7.jpg").then(function(res) {
return uploadImage(sku,"8.jpg").then(function(res) {
return uploadImage(sku,"9.jpg").then(function(res) {
return uploadImage(sku,"10.jpg").then(function(res) {
return sku;
});
});
});
});
});
});
});
});
});
});
So what is the best practice for my purpose?
There is no concept of "correct order" for async calls because they are just that -- asynchronous and they can end at any point.
In your the callback in Q.all(promises).then(...) you should have the responses in the order that you made them, but the order of your console logs may not be in the same order due their asynchronous nature.
In your case, you can probably do it recursively:
function uploadFiles(sku, current, max) {
return uploadImage(sku, current + '.jpg').then(function (sku) {
if (current > max) { return sku; }
return uploadFiles(sku, current + 1, max);
});
}
// use it
uploadFiles('SOME_SKU', 1, 10).then(function (sku) {
// ALL DONE!
});
Try to catch exceptions from the promise.
return Q.all(promises).then(function (res) {
return sku;
})
.catch(function (error) {
// Handle any error from all above steps
});
I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}
I'm new to this kind of problem in javascript and i can't fix this attempt to wait for an asynchronous call combining Angular promise objects and timeouts.
The function onTimeout seems never execute.
getAsyncContent: function (asyncContentInfos) {
var deferObj = $q.defer();
var promiseObj = deferObj.promise;
asyncContentInfos.promiseObject = promiseObj;
var blockingGuard = { done: false };
promiseObj.then(function () {
blockingGuard.done = true;
});
this.wait = function () {
var executing = false;
var onTimeout = function () {
console.log("******************** timeout reached ********************");
executing = false;
};
while (!blockingGuard.done) {
if (!executing && !blockingGuard.done) {
executing = true;
setTimeout(onTimeout, 200);
}
}
};
$http.get(asyncContentInfos.URL, { cache: true })
.then(function (response) {
asyncContentInfos.responseData = response.data;
console.log("(getAsyncContent) asyncContentInfos.responseData (segue object)");
console.log(asyncContentInfos.responseData);
deferObj.resolve('(getAsyncContent) resolve');
blockingGuard.done = true;
return /*deferObj.promise*/ /*response.data*/;
}, function (errResponse) {
var err_msg = '(getAsyncContent) ERROR - ' + errResponse;
deferObj.reject(err_msg);
console.error(err_msg);
});
return {
wait: this.wait
}
}
Client code is something like this:
var asyncVocabulary = new AsyncContentInfos(BASE_URL + 'taxonomy_vocabulary.json');
getAsyncContent(asyncVocabulary).wait();
And AsyncContentInfos is:
function AsyncContentInfos(URL) {
this.URL = URL;
this.responseData = [];
this.promiseObject;
}
$http.get returns a promise which will resolve when the call completes. Promises are a way to make asyncronous more clean and straight lined than plain old callbacks.
getAsyncContent: function (asyncContentInfos) {
return $http.get(asyncContentInfos.URL, { cache: true })
.then(function (response) {
return response.data;
}, function (errResponse) {
console.error(err_msg);
throw errResponse;
});
}
Then using it:
getAsyncContent({...}).then(function(yourDataIsHere) {
});
The nice thing about promises is that they can be easily chained.
getAsyncContent({...})
.then(function(yourDataIsHere) {
return anotherAsyncCall(yourDataIsHere);
})
.then(function(your2ndDataIsHere) {
});