Here is the code I'm using:
const { exec } = require("child_process");
exec("ls -la", (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
It returns "0" and then logs in the console the correct result.
But how to directly return the result instead of console.log?
Some answers have pointed out how to convert it into a Promise.
You can't return a value from that callback, because it would not be passed to anything. What you can do is defining a Promise that passes the stdout to the resolve method. Here's an example:
const { exec } = require("child_process");
function ls() {
return new Promise((resolve, reject) => {
exec("ls -la", (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
reject(error.message);
}
if (stderr) {
console.log(`stderr: ${stderr}`);
reject(stderr);
}
console.log(`stdout: ${stdout}`);
resolve(stdout);
});
});
}
What I am doing here is defining a function that creates a new Promise. The Promise will execute your code (the ls -la call), and will fire an exception if there is an error, rejecting the Promise, or it will solve the Promise if everything is fine, passing the stdout value.
You can then use this Promise with something like this:
ls().then((out) => {
console.log(out);
})
the out variable will contain your stdout.
If you want some function that returns that value, it should be awaited from this function. An example could be this:
async function unwrapLs() {
const stdout = await ls();
return stdout;
}
Note that you can only call unwrapLs() from inside an async function, because you have to await for its value. In fact, this would be equivalent to calling ls() by awaiting it, but you can only do it from inside an async function.
The problem you are experiencing is that it takes time to run an OS command (perhaps indefinite amount of time). Javascript will not just halt execution until this is done, so it is run asynchronously.
A value must be returned and must be returned to the caller before it completes. Using promises, the value returned is a promise.
Since you are calling with the Construct 2 Engine which does not have a mechanism for asynchronous calls, you will not get a result directly.
However, Construct 2 engine has a mechanism for calling back into it, using the Function object
Your javascript should look like this:
const { exec } = require("child_process");
function ls(callback) {
exec("ls -la", (error, stdout, stderr) => {
if (c2_callFunction)
c2_callFunction(callback, [error, stdout, stderr]);
});
}
ls('lsresult')
you can execute like this :
Browser.ExecJS("")
But, to get the results, you must define a Function object called 'lsresult', add parameters to the function (error, stdout, stderr) and handle the results there.
Documentation is here: https://www.construct.net/en/construct-2/manuals/construct-2/plugin-reference/function
Related
So I created a function in my node server which takes in a query string, runs it on my db, and returns the results. I then wanted to use my function asynchronously using async await throughout my routes instead of having nested query within, nested query, within nested query etc.
So here is the code:
const runQuery = queryString => {
console.log("...runQuery")
db.query(queryString, (error, results, fields) => {
if (error) {
console.log("runQuery: FAILURE");
return error;
}
else {
console.log("runQuery: SUCCESS");
return(results);
}
})
}
register.post("/", async (req, res) => {
console.log(req.body);
const results = await runQuery("select * from test1");
res.send(results);
})
The database should have 3 entries, but unfortunately, it returns nothing. Meaning results is an empty variable when it is sent, meaning JS never properly waits for it to capture the db results. How can I use my function asynchronously, and how is this even feasible?
It seems your function "runQuery" does not return a promise, in fact, it's not returning anything. You are using "return" in the callback of the db.query function, not the function "runQuery" itself.
Since runQuery is performing an asynchronous operation, the result ought to be resolved via a promise (which is what the "await" in your request handler is looking for).
I'm not exactly sure but it seems you are using MySql, and I could not find anything on the npm page of the mysql package regarding the query being promisified, so we'll promisify it ourselves:
const runQuery = (queryString) => new Promise((resolve,reject) => {
console.log("...runQuery")
db.query(queryString, (error, results, fields) => {
if (error) {
console.error("runQuery: FAILURE");
reject(error);
} else {
console.log("runQuery: SUCCESS");
resolve(results);
}
})
})
register.post("/", async (req, res) => {
console.log(req.body);
try{
const results = await runQuery("select * from test1");
res.send(results);
}catch(e){
console.error(`ERROR THROWN BY runQuery:`,e);
res.status(500).send({
message: e.message || "INTERNAL SERVER ERROR"
})
}
})
Note that if an error occurs, our promisified function will reject the error and it will NOT be stored in the "results" variable in our request handler. Instead, an error will be thrown which needs to be handled. Therefore, it is always a good practice to put any async/await calls inside a try/catch.
I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});
So in Node we use async functions. And to do that we use callback functions as parameters. But how do I execute the final function after which I want to terminate the code? Just pass the empty function? Here's example:
fs.mkdir('stuff', function(){
fs.readFile('readMe.txt', 'utf8', function(err, data) {
fs.writeFile('./stuff/writeMe.txt', data);
});
});
mkdir has callback function - all fine
readFile has callback function - all fine
writeFile has NOT callback function because that's the last thing I want to do, but then I get an error in console:
"DeprecationWarning: Calling an asynchronous function without callback is deprecated."
Should I every time I do that, pass an empty function as a callback to avoid the error? What's the best practice for this?
Should I every time I do that, pass an empty function as a callback to avoid the error?
No.
What's the best practice for this?
Pass in a function and handle any errors it reports. You also need to handle errors from mkdir and readFile, which currently you're ignoring.
E.g.:
fs.mkdir('stuff', function(err) {
if (err) {
// Do something with the fact mkdir failed
} else {
fs.readFile('readMe.txt', 'utf8', function(err, data) {
if (err) {
// Do something with the fact readFile failed
} else {
fs.writeFile('./stuff/writeMe.txt', data, function(err) {
if (err) {
// Do something with the fact writeFile failed
}
});
}
});
}
});
...which is a fair example of callback hell, which is part of the motivation of using promises instead. You could promisify the fs API (using any of several libs, such as promisify) and do it like this:
fsp.mkdir('stuff')
.then(() => fsp.readFile('readMe.txt', 'utf8'))
.then(data => fsp.writeFile('./stuff/writeMe.txt', data))
.catch(err => {
// Do something with the fact something failed
});
...where fsp is a placeholder for the promisified fs API.
In Node 8.x+, you could use async/await to write synchronous-looking code with those promisified APIs:
// This must be inside an `async` function
try {
fsp.mkdir('stuff');
const data = await fsp.readFile('readMe.txt', 'utf8');
await fsp.writeFile('./stuff/writeMe.txt', data);
} catch (err) {
// Do something with the fact something failed
}
You can use writeFileSync instead.
fs.mkdir('stuff', function(){
fs.readFile('readMe.txt', 'utf8', function(err, data) {
fs.writeFileSync('./stuff/writeMe.txt', data);
});
});
I wasn't sure the best way to title this post, feel free to retitle it as you wish.
I have a method which if it decides to bail, I have it returning null. Otherwise it'll return a promise.
On the receiving side, how can I manage both scenarios? For example I'm trying to figure out how to capture the case where it bails (I get null back from the call to deletePRBucket:
Caller - My test
it('can create a new S3 branch', async () => {
const options = { branch: '11' }
// first lets check if we delete it if it already exists before trying to re-create it
let { error, stdout, stderr } = await Deploy.deletePRBucket(options.branch)
({ error, stdout, stderr } = await Deploy.createPRBucket(options)),
result = JSON.parse(stdout)
expect(result.Location).to.equal(`http://oursite-${options.branch}.s3.amazonaws.com/`)
})
So I get an error here that the values error, stdout, and stderr don't exist because my delete method returns a null if it doesn't attempt to delete (because the bucket doesn't exist). So not sure how to handle the case where it decides to bail and returns null instead of returning the promise.
UPDATED (also included the bucketExists implementation):
Implementation
export async function bucketExists(bucketName){
console.log(`attempting to find bucket oursite-${bucketName}`)
let exists = null
try {
let { error, stdout, stderr } = await exec(`aws s3api head-bucket --bucket oursite-${bucketName}`)
exists = stdout === ""
}
catch(err){
exists = false
}
return exists
}
export async function deletePRBucket(branch){
const bucketExists = await this.bucketExists(branch)
if(!bucketExists) {
return new Promise((resolve) => {
resolve({ error, stdout, stderr })
})
}
return exec(`aws s3 rb s3://oursite-${branch} --force`)
}
I mean I suppose I could just return an object { error, stdout, stderr } and check that stdout is null or something like that, maybe return a promise? if(!bucketExists) return new Promise(() => { error, stdout, stderr })
I think this is a question of error handling:
export async function deletePRBucket(branch){
const bucketExists = await this.bucketExists(branch)
if(!bucketExists) throw new Error("bucket");
return exec(`aws s3 rb s3://oursite-${branch} --force`)
}
...
try{
let { error, stdout, stderr } = await Deploy.deletePRBucket(options.branch);
}catch(e){
console.log("error");
}
Provide a default object to be used if the promise returns null.
let { error, stdout, stderr } = (await Deploy.deletePRBucket(options.branch)) || {};
I've seen a similar question once, but I can't for the life of me figure out why this isn't working. I have a pretty simple program below that should wrap the exec function and return the result. However all it returns is undefined. Here's the function:
var exec = require('child_process').execSync;
quickexec = function(command) {
exec(command, function(error, stdout, stderr) {
if(error) {
return error;
} else {
return stdout;
}
});
};
I call it like this console.log(quickexec('echo -n $USER')); and I get undefined everytime. However if I change the return in my function to a console.log it works. I thought that it was an async problem which is why I started using execSync, but it didn't change anything.
quickexec() does not actually return anything. The return inside it are in the async callback which happens long after quickexec() has already returned. You can't synchronously return an asynchronous result. This is a common issue when learning how to do proper asynchronous programming in node.js.
If you need a synchronous result, you can use execsync(), but usually the best design is to use the asynchronous result in the callback.
var quickexec = function(command, callback) {
exec(command, function(error, stdout, stderr) {
if(error) {
callback(error);
} else {
callback(null, stdout);
}
});
};
quickexec('echo -n $USER', function(err, result) {
// use the result here in the callback
if (err) {
console.log(err);
} else {
console.log(result);
}
});