I need to get a value of an asynchronous function. I tried to use Promise, but that does not work:
const res = new Promise(function (resolve, reject) {
gm(readStream).size({ bufferStream: true }, function (err, size) {
if (!err) resolve(size)
})
})
console.log(res)
The result I get is Promise { <pending> }
Promises are an abstraction for callbacks, not magic. They can't make asynchronous code synchronous.
The correct solution is:
const res = new Promise(function (resolve, reject) {
gm(readStream).size({ bufferStream: true }, function (err, size) {
if (err) reject(err);
else resolve(size);
})
});
res.then(function(promiseResolutionValue) {
console.log(res)
})
You could also use async / await here:
const getSize = readStream => {
return new Promise(function (resolve, reject) {
gm(readStream).size({ bufferStream: true }, function (err, size) {
if (err) reject(err);
else resolve(size);
})
});
}
let printSize = async readStream => {
console.log(`Size is ${await getSize(readStream)}`);
}
Or, if you're using NodeJS (Version 8+), you might be able to adapt your function to use util.promisify.
Other Promise libraries, such as Bluebird, also offer such functions, to easily convert 'standard' node-style functions (functions that have a callback with err, data as arguments) into promise-returning equivalents.
Or just use the callback.
Your code should be looked like this:
const res = new Promise(function (resolve, reject) {
gm(readStream).size({ bufferStream: true }, function (err, size) {
if (!err) resolve(size)
else reject(err)
})
})
function onResolved(data) {
console.log('data: ', data);
}
function onRejected(err) {
console.log('err:', err);
}
res.then(onResolved, onRejected);
Promise does not make your code synchronous, it lets you control when you want to get the result, not immediately like callback style.
Related
I am new to Node.js and am able to run these commands one by one one using promises:
let promise1 = new Promise(function (resolve, reject) {
sftp.connect({
host: host,
username: user,
privateKey: fs.readFileSync(pemfile)
}).then(() => {
return sftp.get(remotePath, fs.createWriteStream(localPath)); //This writes from a remote file to a local file
}).then(() => {
sftp.end();
resolve();
})
.catch(err => {
console.error(err.message);
reject(err);
});
});
await promise1;
let promise2 = new Promise(function (resolve, reject) {
fs.readFile(localPath, 'utf8', function (err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
let data = await promise2;
This works but I know this is not the best way to do this. Is there a better way to do this?
await can be used directly with methods that return promise like sftp.connect, sftp.get, sftp.end so you can use it directly and the function will wait until the step is completed. Only fs.readfile does not return a Promise but we can call fs.promises.readFile() that returns promise then we can use await.
The code can be simplified:
try {
await sftp.connect({
host: host,
username: user,
privateKey: fs.readFileSync(pemfile)
})
await sftp.get(remotePath, fs.createWriteStream(localPath));
await sftp.end();
let fileData = await fs.promises.readFile(localPath, 'utf8');
console.log(fileData);
} catch (error) {
console.error(error.message);
}
The try-catch block has been added to capture errors from any action.
I have made a promise, from which I want to convert a webkitGetAsEntry fileobject to a regular File object.
var getAsFile = async function(fileEntry) {
try {
return new Promise((resolve, reject) => fileEntry.file(resolve, reject));
} catch (err) {
console.log(err);
}
}
However, after alot of changes to my code, I would really want to return an object in which I have a path to the file as well. I.e.
var getAsFile = async function(fileEntry) {
try {
return new Promise((resolve, reject) => return { 'file': fileEntry.file(resolve, reject), 'filepath': fileEntry.fullPath};);
} catch (err) {
console.log(err);
}
}
When the following promise is resolved, only the file is returned and not the object.
getAsFile(entries[i]).then(function (file) {
//Can only get the file, and not file.filepath
})
What fundamental knowledge about promises am I missing here, and how do I achieve returning an object with file and filepath?
You need to pass the object to the resolve function:
var getAsFile = function(fileEntry) {
return new Promise((resolve, reject) => fileEntry.file(
file => resolve({file, filepath: fileEntry.fullPath}),
reject
));
}
Note that it is not necessary to declare that function as async since you return a promise and don't use await.
The error handling should happen at the place where this function is called.
Fixed: <%- locals.devices -%>
So what I'm trying to achieve, as said in the title, is sending an array of json objects result from promises.
const switch_p = new Promise(function (resolve, reject) {
conn.query('SELECT * FROM switch', (err, switch_data) => {
if (err) throw err;
resolve(switch_data);
});
});
const ap_p = new Promise etc..
...
...
resolve(ap_data);
const server_p = new Promise etc..
...
...
resolve(server_data);
const router_p = new Promise etc..
...
...
resolve(router_data);
These are the 4 promises from which I want to get the value and render them afterwards.
Promises.all function: - I've tried more cases of rendering the values but without success everytime..
This is the last try:
Promise.all([switch_p, ap_p, server_p, router_p]).then(values => {
const responses = values.map(response => values)
return Promise.all(responses)
}).then(responses => {
console.log(responses);
data = responses
console.log(data); // here its shows that data has content
res.render('admin-panel', {
layout: 'layoutAdmin',
locals: {_id: sess_id, uname: sess_uname, rol: sess_rol, devices: data}
});
});
In view I just try to show the content from devices (<%- devices -%>), which I know it'll be something like this:
[ [ [Object],
[Object],
[Object],
........
but it says "devices is not defined".. and I don't know why.
Also if there is a solution on how to do that without breaking the code in multiple functions that'll be cool.
<<<<<<<<<<<<< Edit 1 >>>>>>>>>>>>>
Let's forget about the ap_p promise for now.
This are the promises functions:
const switch_p = new Promise(function (resolve, reject) {
conn.query('SELECT * FROM switch', (err, switch_data) => {
if (err) reject(err)
else resolve(switch_data);
});
});
const server_p = new Promise(function (resolve, reject) {
conn.query('SELECT * FROM server', (err, server_data) => {
if (err) reject(err)
else resolve(server_data);
});
});
const router_p = new Promise(function (resolve, reject) {
conn.query('SELECT * FROM router', (err, router_data) => {
if (err) reject(err)
else resolve(router_data);
});
});
And the promise all function, although I don't know if I handled the error in a good way..
Promise.all([switch_p, server_p, router_p]).then(responses => {
console.log(responses);
res.render('admin-panel', {
'layout': 'layoutAdmin',
'locals': {'_id': sess_id, 'uname': sess_uname, 'rol': sess_rol, 'devices': responses}
});
})
.catch(function (error) {
console.log(error);
throw (error);
});
console.log(responses) does work fine, and the single error that I get is still "devices is not defined".
Where the promises are created, throwing does not have the desired effect. reject() must be called.
Replace ...
`if (err) throw err`;
with ...
if (err) reject(err).
In the Promsie.all() chain,
purge the first .then(...)
in the second .then(), purge data = responses; and work with responses
add a .catch() handler.
You should end up with ...
const switch_p = new Promise(function (resolve, reject) {
conn.query('SELECT * FROM switch', (err, switch_data) => {
if (err) reject(err)
else resolve(switch_data);
});
});
// etc.
and ...
Promise.all([switch_p, ap_p, server_p, router_p])
.then(responses => {
console.log(responses);
res.render('admin-panel', {
'layout': 'layoutAdmin',
'locals': { '_id': sess_id, 'uname': sess_uname, 'rol': sess_rol, 'devices': responses }
});
})
.catch(function(error) {
// Handle error here.
// Either return a value (to put promise chain back on the success path) ...
// ... or throw/rethrow error (to continue on the error path)
});
So, I found out what the problem was. I should've render <%- locals.devices -%>... yes a dumb mistake by me. Thanks you for your implication.
Normally if I was going to run multiple mongoose queries I would use the built in promise to chain them all together. In this case, the user chooses which schemas to search. This could be one of them or both. The following example uses the post data to define which schemas to search and if one is false, it should continue through the promise chain. Right now the final promise is being called before the queries.
Example in my express controller:
app.post('/custom-search', function (req, res) {
var single = false
var multi = false
if(req.body.single){
var single = true
}
if(req.body.multi){
var multi = true
}
var promise = new Promise(function (resolve, reject) {
if(multi){
multiSchema.find({}, function (err, result) {
if(!err){
console.log(result);
resolve()
}
})
}else{
resolve()
}
}).then(function (value) {
if(single){
singleSchema.find({}, function (err, result) {
if(!err){
console.log(result);
resolve()
}
})
}else{
resolve()
}
}).then(function (value) {
console.log("done");
})
})
});
output:
>done
>[singleResults]
>[multiResults]
done should be printing last so that is the first problem.
Like we discussed, few things had to be clean up. First by actually using and returning the promise for it work properly, Second, creating a mini-promise within your first .then() to resolve and reject your single conditional statement. And third, handling/catching promises.
I wrote a pseudo version of your code to illustrate my point of view, hopefully it may be of a good use.
app.get('/custom-search', function (req, res) {
// Manipulating values to test responses
var single = false;
var multi = true;
var promise = new Promise(function (resolve, reject) {
if (multi) {
setTimeout(function () {
resolve('MULTI RESOLVED!');
}, 3000);
} else {
reject('MULTI REJECTED!');
}
})
.then(function (value) {
new Promise(function (resolve, reject) {
if (single) {
setTimeout(function () {
resolve('SINGLE RESOLVED!');
}, 3000);
} else {
reject('SINGLE REJECTED!');
}
})
.catch(function (error) {
console.log('SINGLE ERROR!', error);
})
.then(function (value) {
console.log('SINGLE DONE', value);
});
})
.catch(function (error) {
console.log('MULTI ERROR!', error);
});
return promise;
});
I got 4 promises here, and I thought that it would run the first one, then wait until its finished, THEN run the next one, wait till finished and THEN run the next one etc..
But what happens here is that it runs all of them all at once and does not wait for anything to finish.
This is my promise chain:
//
// Run the promises
//
findBanks
.then(findReceipts)
.then(findExpenses)
.then(sendResult)
.catch(err => {
console.error(err);
console.log("getbankAccountReport ERR: " + err);
res.json({error:true,err})
});
This is the output from my console.log
=====findAllBank=====
=====findAllReceipt=====
=====findAllExpense=====
=====RESOLVE findAllBank=====
=====sendResult=====
=====RESOLVE sendResult=====
=====RESOLVE findAllReceipt=====
=====RESOLVE findAllExpense=====
Am I not understanding promises correct or?
Anyway here is my nodejs controller:
exports.getBankAccountReport = function(req, res) {
//
// Find all bank accounts
//
var bankModel = require('../models/bankModel');
var bankTable = mongoose.model('bankModel');
var bankArray = [];
var findAllBank = new Promise(
(resolve, reject) => {
console.log("=====findAllBank=====")
bankTable.aggregate([
...lots of mongo stuff...
],function(err, data) {
if (!err) {
bankArray = data;
console.log("=====RESOLVE findAllBank=====")
resolve(data);
} else {
reject(new Error('findBank ERROR : ' + err));
}
});
});
//
// Find the RECEIPT for each bank account
//
var receiptModel = require('../models/receiptModel');
var receiptTable = mongoose.model('receiptModel');
var receiptArray = [];
var findAllReceipt = new Promise(
(resolve, reject) => {
console.log("=====findAllReceipt=====")
receiptTable.aggregate([
...lots of mongo stuff...
], function (err, data) {
if (!err) {
receiptArray = data;
console.log("=====RESOLVE findAllReceipt=====")
resolve(data);
} else {
reject(new Error('findReceipts ERROR : ' + err));
}
});
});
//
// Find the EXPENSE for each bank account
//
var expenseModel = require('../models/expenseModel');
var expenseTable = mongoose.model('expenseModel');
var expenseArray = [];
var findAllExpense = new Promise(
(resolve, reject) => {
console.log("=====findAllExpense=====")
expenseTable.aggregate([
...lots of mongo stuff...
], function (err, data) {
if (!err) {
expenseArray = data;
console.log("=====RESOLVE findAllExpense=====")
resolve(data);
} else {
reject(new Error('findExpense ERROR : ' + err));
}
});
});
var sendResult = function(data) {
var promise = new Promise(function(resolve, reject){
console.log("=====sendResult=====")
res.json({error:false,
"bank":bankArray,
"receipt":receiptArray,
"expense":expenseArray})
console.log("=====RESOLVE sendResult=====")
resolve();
});
return promise;
};
//
// Run the promises
//
findAllBank
.then(findAllReceipt)
.then(findAllExpense)
.then(sendResult)
.catch(err => {
console.error(err);
console.log("getbankAccountReport ERR: " + err);
res.json({error:true,err})
});
}
You need to wrap your Promises in functions
var findAllBank = function() {
return new Promise(
(resolve, reject) => {
console.log("=====findAllBank=====")
bankTable.aggregate([
...lots of mongo stuff...
],function(err, data) {
if (!err) {
bankArray = data;
console.log("=====RESOLVE findAllBank=====")
resolve(data);
} else {
reject(new Error('findBank ERROR : ' + err));
}
});
});
});
When resolved, the next function in the chain will be called with the data passed in the resolve() function.
Do no confuse the Promise and the function that builds it
When you create a new Promise(executor), you instanciate a new object that will have two methods (functions of an object), .then(resolveCB [, rejectCB]) and .catch(rejectCB).
The aime is to know, whenever a process is done, if it was successful or if it failed, and the continue accordingly.
var myFirstPromise = new Promise(function executor(resolve, reject) { resolve('resolved'); });
In other words, those methods are used to continue your process once the promise defined by executor is settled. It can either get fulfilled and call the resolveCB callback (usingthen), or rejected and call the rejectCBcallback (using both then and catch). A callback (resolveCB or rejectCB) is a function, not a Promise itself, even if the callback might return a Promise.
myFirstPromise
.then(function resolveCB(result) { console.log(result); }) //you can use a 2nd callback for rejection at this point
.catch(function rejectCB(err) { console.log(err); });
myFirstPromise
.then(
function resolveCB(result) { console.log(result); } // if resolved
, function rejectCB(err) { console.log(err); } // if rejected
)
.catch(function rejectCB(err) { console.log(err); }); // NEVER forget the last catch, just my 2cents :)
We saw the inputs of .then() and .catch() but what about their return value? The both of them will return a new Promise. That's why you can chain the .then()'s and .catch()'s.
myFirstPromise
.then(function resolveCB1(result) { console.log(result); })
.then(function resolveCB2(result) { console.log(result); }) // console.log is called but result is undefined
.catch(function rejectCB1(err) { console.log(err); });
myFirstPromise
.then(function resolveCB3(result) {
throw 'I threw an exception'; // an exception is thrown somewhere in the code
console.log(result);
})
.then(function resolveCB4(result) { console.log(result); })
.catch(function rejectCB2(err) { console.log(err); }); // a promise in the chain get rejected or error occured
In the previous example, we see that our second .then() is hit but result is undefined. The promise returned by first .then() fullfiled but no value as been passed by the executor to the resolve callback resolveCB2. In the second case, an exception occured in resolveCB3, it gets rejected so rejectCB2 is called. If we want our resolve callbacks to receive an argument, we have to notify the exector. To do so, the simplest way is for the callbacks to return a value:
myFirstPromise
.then(function resolveCB1(result) {
console.log(result);
result += ' again';
return result;
})
.then(function resolveCB2(result) {
console.log(result);
return result;
})
.catch(function rejectCB1(err) { console.log(err); });
Now that's said, you've got all the pieces together for understanding a Promise. Let's try to sum it up in a cleaner way:
var myFirstPromise = new Promise(function executor(resolve, reject) { resolve('resolved'); })
, resolveCB = function resolveCB(result) {
console.log(result);
result += ' again';
return result;
}
, resolveLastCB = function resolveLastCB(result) {
console.log(result);
result += ' and again';
return result;
}
, justLog = function justLog(result) {
console.log(result);
return result;
}
;
myFirstPromise
.then(resolveCB)
.then(resolveLastCB)
.then(justLog)
.catch(justLog);
You can now chan them nicely, it's cool and all
myFirstPromise
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveCB)
.then(resolveLastCB)
.then(justLog)
.catch(justLog);
But what if your Promise chain 'really' changes and you need to get rid of myFirstPromise and start with resolveCB instead ? It's just a function, it can be executed but doesn't have any .then() or .catch() method. It's not a Promise. You can't do resolveCB.then(resolveLastCB), it will thow an error resolveCB.then( is not a function or something similar. You might think this is a gross mistake, I didn't call resolveCB and resolveCB().then(resolveLastCB) should work? Unfortunalty for those who thought about that, it's still wrong. resolveCB returns a string, some characters, not a Promise.
In order to avoid this kind of maintenance issue, you should know that the resolve and reject callbacks can return a Promise instead of a value. To do so, we'll use what's called the factory pattern. In simple words, the factory pattern is about instanciating new object using a (static) function instead of using the constructor directly.
var myFirstPromiseFactory = function myFirstPromiseFactory() {
/*
return new Promise(function executor(resolve, reject) {
resolve('resolved');
});
if you just need to resolve a Promise, this is a quicker way
*/
return Promise.resolve('resolved');
}
, resolveFactory = function resolveFactory(result) {
return new Promise(function executor(resolve, reject) {
result = result || 'I started the chain';
console.log(result);
result += ' again';
return resolve(result); // you can avoid the return keyword if you want, I use it as a matter of readability
})
}
, resolveLastFactory = function resolveLastFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
result += ' and again';
return resolve(result);
});
}
, justLogFactory = function justLogFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
return resolve(result);
});
}
;
myFirstPromiseFactory() //!\\ notice I call the function so it returns a new Promise, previously we started directly with a Promise
.then(resolveFactory)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Now you can switch easily, just call the first one
resolveFactory()
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
justLogFactory('I understand Javascript')
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
Factory functions might come handy when iterating over an array. It might be used to produce an array of promise given an input:
var myFirstPromiseFactory = function myFirstPromiseFactory() {
/*
return new Promise(function executor(resolve, reject) {
resolve('resolved');
});
if you just need to resolve a Promise, this is a quicker way
*/
return Promise.resolve('resolved');
}
, resolveFactory = function resolveFactory(result) {
return new Promise(function executor(resolve, reject) {
result = result || 'I started the chain';
console.log(result);
result += ' again';
return resolve(result); // you can avoid the return keyword if you want, I use it as a matter of readability
})
}
, resolveLastFactory = function resolveLastFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
result += ' and again';
return resolve(result);
});
}
, justLogFactory = function justLogFactory(result) {
return new Promise(function executor(resolve, reject) {
console.log(result);
return resolve(result);
});
}
, concatValues = function concatValues(values) {
return Promise.resolve(values.join(' '));
}
, someInputs = [
'I am an input'
, 'I am a second input'
, 'I am a third input'
, 'I am yet an other input'
]
;
myFirstPromiseFactory() //!\\ notice I call the function so it returns a new Promise, previously we started directly with a Promise
.then(resolveFactory)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Now you can switch easily, just call the first one
resolveFactory()
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
justLogFactory('I understand Javascript')
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);
// Using a factory functions to create an array of promise usable with Promise.all()
var promiseArray = someInputs.map(function(input) {
return justLogFactory(input);
});
Promise.all(promiseArray)
.then(concatValues)
.then(resolveLastFactory)
.then(justLogFactory)
.catch(justLogFactory);