I'm trying to get my head around promises in JavaScript. I feel like I know what a promise is. However, I don't understand how to use them. In an attempt to learn, I decided to query a database from Node.js. In my code, I have one JavaScript file called test.js. Test.js looks like this:
Test.js
'use strict';
module.exports = function (app) {
var customerService = require('customer.js')(app);
var getCustomerTest = function() {
customerService.getCustomer(1).then(
function (customer) { console.log(customer); },
function (error) { console.log(error); }
);
};
};
Customer.js
'use strict';
module.exports = function(app) {
var _ = require('lodash');
var db = require('db');
return {
getCustomer: function(customerID) {
try {
console.log('querying the database...');
var database = db.connect(CONNECTION_STRING);
database.query('select * from customers where [ID]="' + customerID + '", function(error, result, response) {
if (error) {
// trigger promise error
} else {
// This throws an exception because displayMessage can't be found.
this.displayMessage('Success');
// trigger promise success
}
});
} catch (ex) {
// trigger promise error
}
},
displayMessage: function(message) {
console.log(new Date() + ' - ' + message);
}
};
};
I'm struggling trying to setup the promise in getCustomer. Especially since the call to the database call happens asynchronously. I feel like my call to customerService.getCustomer is the correct approach. However, once inside of getCustomer, I have two issues:
How do I setup / return my promise?
Why can't I call displayMessage after the database query is done? How do I do this?
Thank you JavaScript whiz!
"Why can't I call displayMessage after the database query is done"
You are getting the error because this is not referencing the object that contains getCustomer and displayMessage. This is because you are in a callback function and the context has changed.
You need to save a reference to the correct context and use that to access displayMessage
getCustomer: function(customerID) {
//saving the context
var that = this;
try {
...
database.query('select * from customers where [ID]="' + customerID + '", function(error, result, response) {
...
// Now use it here to call displayMessage
that.displayMessage('Success');
...
}
});
} catch (ex) {
...
}
},
"How do I setup / return my promise?"
You will need a promise library (unless you plan to make your own). For this I will show the use of the q library
There are a couple ways of doing this, but I will show the use of deferreds.
The basic process is:
Create a deferred object in a function that will call an async function/method.
Return the promise object
Set the appropriate callbacks for then/fail/fin etc on the promise object.
In the callback of the async function resolve or reject the deferred, passing any arguments that will be needed in the callbacks.
The appropriate callbacks that were set in step 2 will then be called in order.
Code
var Q = require("q");
...
getCustomer:{
var deferred = Q.defer(),
database = db.connect(CONNECTION_STRING);
database.query("some query", function(error, result, response) {
if(error){
//Anything passed will be passed to any fail callbacks
deferred.reject(error);
} else {
//Anything passed will be passed to any success callbacks
deferred.resolve(response);
}
});
//Return the promise
return deferred.promise;
}
...
customerService.getCustomer(1).then(function (customer) {
console.log(customer);
}).fail(function (error) {
console.log(error);
}).done();
The q library has quite a few helpful api functions. Some help with getting promises with Node async functions. Read the Adapting Node section of the readme to see how it is done.
JSFiddle Demo demonstrating in browser use of q
Related
I am trying to use AWS Javascript SDK for accessing S3. I am using the Promises with 'Q' library. However it is not working.
I have set the Q dependency also.
var Q = require('q');
AWS.config.setPromisesDependency(Q);
Here's the code I have:
var listBucketPromise = s3.listBuckets().promise();
listBucketPromise.then(
function (response) {
console.log(" response = " + response );
} ,
function (error) {
console.log(" error = " + error);
}
);
This shows on console :
response = function (resolve, reject) {
self.on('complete', function(resp) {
if (resp.error) {
reject(resp.error);
} else {
// define $response property so that it is not enumberable
// this prevents circular reference errors when stringifying the JSON object
resolve(Object.defineProperty(
resp.data || {},
'$response',
{value: resp}
));
}
});
self.runTo();
}
I have a valid s3 client set correctly. The callback format works:
s3.listBuckets(function (err, data) {
console.log(data);
});
Why is the promise code not working ?
You need to pass a Promise constructor to setPromisesDependency. The Q function that you used does not expect a callback, when called with the typical promise executor callback it just returned a promise fulfilled with that function value.
You can use Q.Promise instead, which also is documented in this blog post's example:
// Use Q implementation of Promise
AWS.config.setPromisesDependency(require('Q').Promise);
Let's say I have some code that looks like this:
var doSomething = function(parameter){
//send some data to the other function
return when.promise(function(resolveCallback, rejectCallback) {
var other = doAnotherThing(parameter);
//how do I check and make sure that other has resolved
//go out and get more information after the above resolves and display
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
How do I ensure that var other has completely resolved before finishing and resolving the first doSomething() function? I'm still wrapping my head around Nodes Async characteristic
I really didn't know how else to explain this, so I hope this makes sense! Any help is greatly appreciated
EDIT: In this example, I am deleting things from an external resource, then when that is done, going out the external resource and grabbing a fresh list of the items.
UPDATED CODE
var doSomething = function(parameter){
//send some data to the other function
doAnotherThing(parameter).then(function(){
//now we can go out and retrieve the information
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
The return of doAnotherThing appears to be a promise. You can simply chain a then and put your callback to utilize other. then also already returns a promise. You can return that instead.
// Do stuff
function doSomething(){
return doAnotherThing(parameter).then(function(other){
// Do more stuff
return other
});
}
// Usage
doSomething().then(function(other){
// other
});
Below is how to accomplish what you're trying to do with bluebird.
You can use Promise.resolve() and Promise.reject() within any function to return data in a Promise that can be used directly in your promise chain. Essentially, by returning with these methods wrapping your result data, you can make any function usable within a Promise chain.
var Promise = require('bluebird');
var doSomething = function(parameter) {
// Call our Promise returning function
return doAnotherThing()
.then(function(value) {
// Handle value returned by a successful doAnotherThing call
})
.catch(function(err) {
// if doAnotherThing() had a Promise.reject() in it
// then you would handle whatever is returned by it here
});
}
function doAnotherThing(parameter) {
var s = 'some data I got from the url';
// Return s wrapped in a Promise
return Promise.resolve(s);
}
You can use the async module and its waterfall method to chain the functions together:
var async = require('async');
async.waterfall([
function(parameter, callback) {
doSomething(parameter, function(err, other) {
if (err) throw err;
callback(null, other); // callback with null error and `other` object
});
},
function(other, callback) { // pass `other` into next function in chain
doAnotherThing(other, function(err, result) {
if (err) throw err;
callback(null, result);
})
}
], function(err, result) {
if (err) return next(err);
res.send(result); // send the result when the chain completes
});
Makes it a little easier to wrap your head around the series of promises, in my opinion. See the documentation for explanation.
I'm using Meteor (1.0.3) in general, but for one particular case I'm using a raw server side route to render a file -- so I'm outside a Meteor method.
I'm using node fs.writeFile/fs.readFile and exec commands to call out to Linux command-line utilities too.
My only point in brining this up is that the node calls are async of course. And so I'm opted to use the node Q library in order to manage async callbacks.
This all worked until I added a line to call out to the MongoDB database.
A call like so:
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
Produces the following error:
[Error: Can't wait without a fiber]
The error only occurs when I wrap the function in a Promise.
For example, something like this will throw:
getRecordExample = function () {
var deferred = Q.defer();
var record_name = Mongo_Collection_Name.findOne({_personId: userId}, {fields: {'_id': 0}});
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
If I use the Meteor Fibers library I don't get the error:
getRecordExample = function () {
var deferred = Q.defer();
Fiber = Npm.require('fibers');
var record_name
Fiber(function () {
record_name = Mongo_Collection_Name.findOne({_personId: userId});
}).run()
// do something
// if no error
deferred.resolve(record_name);
return deferred.promise;
}
but, the record_name variable is undefined outside the fiber, so I don't have a way to pass the variable outside of the Fiber scope as far as I can tell.
A More Precise Example
This is a little long, so you have to scroll down to see it all. I'm basically building a workflow here so there are processes and subprocesses.
// both/routes.js
Router.route('/get-route', function(req, res) {
// get the userId then start the workflow below
// using Promises here because these were firing concurrently
Q(userId)
.then(process_1)
.then(process_2)
.done();
}, { name: 'server-side-ir-route', where: 'server' }
// server.js
process_1 = function (userId) {
sub_process_1(userId);
sub_process_2(userId);
return userId;
}
process_2 = function (userId) {
sub_process_3(userId);
sub_process_4(userId);
return userId;
}
sub_process_1 = function (userId) {
var result = get_record_1(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future() // <-- if no future() here, the exception is thrown.
sub_process_2 = function (userId) {
var result = get_record_2(userId);
// do stuff with result
// using Q library to call out to async fs.writeFile, return Promise
fs_writeFile_promise(result)
.catch(function (error) {
console.log('error in sub_process_1_write', error);
})
.done(function () {
console.log('done with sub_process_1');
}
return userId;
}.future()
// async because of I/O operation (I think)
get_record_1 = function (userId) {
var record_1 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_1;
}
get_record_2 = function (userId) {
var record_2 = Mongo_Collection_Name.findOne({'userId': userId});
// do stuff
return record_2;
}
// async operation using Q library to return a Promise
fs_writeFile_promise = function (obj) {
var deferred = Q.defer();
fs.writeFile(obj.file, obj.datas, function (err, result) {
if (err) deferred.reject(err);
else deferred.resolve('write data completed');
});
return deferred.promise;
}
For now, lets assume that the process_2 function is exactly like process_1
Also, we should assume I have console.log('step_start') and console.log('step_end') in each function. This is what it would look like on the command line:
start processes
end processes
start processes 1
end processes 1
start processes 2
start sub processes 1
getting record 1
start sub processes 2
getting record 2
returning record 1
end sub processes 1
called writeData in sub process 1
returning record 2
called writeData in sub process 2
end processes 2
ending sub processes 1
The reason I had to place a Fiber (future) on the sub_process_1() function was because when I placed the function process_1() in the Q chain at the top I got the Error: Can't wait without a fiber].
If I remove the process_1() in the Q chain at the top and remove the .future() from sub_process_1() no exception is thrown.
Questions
Why does calling out to a Mongo collection within a Promise cause a
fiber error within a Meteor application?
Does calling a async function within a sync function in general cause the sync function to become a async function?
How do I solve this problem?
The most common way to solve this is wrap your asynchronous callbacks that use Meteor functions in Meteor.bindEnvironment().
If you are using the Meteor core WebApp package to handle your server side route, the code would be like this (also in meteorpad):
WebApp.connectHandlers.use(
'/test',
Meteor.bindEnvironment(function(req, res, next) {
var someSyncData = Players.findOne();
res.write(JSON.stringify(someSyncData));
res.end();
})
);
Working with fibers or promises yourself is unnecessary unless you are trying to get multiple async events to run concurrently.
To deal with file reading or other functions that are not already synchronous, Meteor also provides Meteor.wrapAsync() to make them synchronous.
There are also packages and a help page that give you other high level alternatives.
I think this is a really stupid question but I'm having a hard time wrapping my head around promises.
I'm using Q (for nodejs) to sync up a couple of async functions.
This works like a charm.
var first = function () {
var d = Q.defer();
fs.readdir(path,function(err,files){
if(err) console.log(err);
d.resolve(files);
});
return d.promise;
};
var second = function (files) {
var list = new Array;
files.forEach(function(value, index){
var d = Q.defer();
console.log('looking for item in db', value);
db.query(
'SELECT * FROM test WHERE local_name =? ', [value],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
d.resolve(rows[0]);
} else {
var itemRequest = value;
getItemData(itemRequest);
}
}
);
list.push(d.promise);
});
return Q.all(list);
};
first()
.then(second)
.done(function(list){
res.send(list);
});
The problem I have is with this little function:
getItemData(itemRequest)
This function is filled with several of callbacks. The promise chain runs through the function just fine but ignores all the callbacks I use ( eg several XHR calls I make in the function).
A simplified version of the function looks like this (just to give you an idea):
function getItemData(itemRequest){
helper.xhrCall("call", function(response) {
var requestResponse = JSON.parse(response)
, requestInitialDetails = requestResponse.results[0];
downloadCache(requestInitialDetails,function(image) {
image = localImageDir+requestInitialDetails.image;
helper.xhrCall("call2", function(response) {
writeData(item,image,type, function(){
loadData(item);
});
});
} else {
writeData(item,image,type, function(){
loadData(item);
});
}
});
});
The xhr function I use looks like this:
xhrCall: function (url,callback) {
var request = require("request")
, colors = require('colors');
request({
url: url,
headers: {"Accept": "application/json"},
method: "GET"
}, function (error, response, body) {
if(!error){
callback(body);
}else{
console.log('Helper: XHR Error',error .red);
}
});
}
So my questions:
Can I leave the function unaltered and use the callbacks that are in place ánd the promise chain?
Or do I have to rewrite the function to use promises for the XHR?
And if so, How can I best write my promise chain? Should I reject the initial promise in the forEach?
Again, sorry if this is a really stupid question but I don't know what the right course of action is here.
Thanks!
[EDIT] Q.nfcall, I don't get it
So I've been looking into Q.nfcall which allows me to use node callbacks. Bu I just don't understand exacly how this works.
Could someone give a simple example how I would go about using it for a function with several async xhr calls?
I tried this but as you can see I don't really understand what I'm doing:
var second = Q.nfcall(second);
function second (files) {
[EDIT 2]
This is the final funcction in my getitemdata function callback chain. This function basically does the same as the function 'second' but I push the result directly and then return the promise. This works as stated, but without all the additional callback data, because it does not wait for the callbacks to return with any data.
function loadData(item) {
var d = Q.defer();
db.query(
'SELECT * FROM test WHERE local_name =? ', [item],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
list.push(d.promise);
}
}
);
});
return Q.all(list);
};
Your answer is not really clear after your second edit.
First, on your orignal question, your getItemData has no influence on the promise chain.
You could change you the function's call signature and pass your deferred promise like so.
getItemData(itemRequest, d)
and pass this deferred promises all the way to your xhrCall and resolve there.
I would re-write your whole implementation and make sure all your functions return promises instead.
Many consider deferred promises as an anti-pattern. So I use use the Promise API defined in harmony (the next javascript)
After said that, I would re-implement your original code like so (I've not tested)
var Promise = Promise || require('es6-promise').Promise // a polyfill
;
function errHandler (err){
throw err
}
function makeQuery () {
var queryStr = 'SELECT * FROM test WHERE local_name =? '
, queryOpt = {local_name: String}
;
console.log('looking for item in db', value)
return new Promise(function(resolve, reject){
db.query(queryStr, [value], queryOpt, function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
resolve(rows[0]);
} else {
// note that it returns a promise now.
getItemData(value).then(resolve).catch(errHandler)
}
})
})
}
function first () {
return new Promise(function(resolve, reject){
fs.readdir(path, function(err, files){
if (err) return reject(err)
resolve(files)
})
})
}
function second (files) {
return Promise.all(files.map(function(value){
return makeQuery(value)
});
}
first()
.then(second)
.then(res.send)
.catch(errHandler)
Note that there is no done method on the Promise API.
One down side of the new Promise API is error handling. Take a look at bluebird.
It is a robust promise library which is compatible with the new promise API and has many of the Q helper functions.
As far as I can tell, you need to return a promise from getItemData. Use Q.defer() as you do in second(), and resolve it when the callbacks complete with the data. You can then push that into list.
To save code, you can use Q.nfcall to immediately call a node-style-callback function, and return a promise instead. See the example in the API docs: https://github.com/kriskowal/q/wiki/API-Reference#qnfcallfunc-args
I use promises in my website (still learning) and I would like to know if there is a difference between this:
return promise
.then(ctxTransport.getTransportById(idTran, transport))
.then(checkLocking)
.fail(somethingWrong);
and this:
return promise
.then(function () { return ctxTransport.getTransportById(idTran, transport); })
.then(function () { return checkLocking(); })
.fail(somethingWrong);
With the first implementation sometimes I got errors.
var getTransportById = function (transportId, transportObservable, forceRemote) {
// Input: transportId: the id of the transport to retrieve
// Input: forceRemote: boolean to force the fetch from server
// Output: transportObservable: an observable filled with the transport
...
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
transportObservable(data.results[0]);
}
};
function checkLocking() {
var now = new Date();
transport().lockedById(5);
transport().lockedTime(now);
return ctxTransport.saveChanges(SILENTSAVE);
}
function somethingWrong(error) {
var msg = 'Error retreiving data. ' + error.message;
logError(msg, error);
throw error;
}
Thanks.
When passing functions in the promise chain, you're supposed to pass the function names without arguments or the (), or as in the second case, anonymous functions. This is because Q will call it for you with the result/return value of the previous promise resolution.
Therefore, .then(ctxTransport.getTransportById(idTran, transport)) is semantically incorrect, since you're not passing a function, but the return value of ctxTransport.getTransportById.