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)) || {};
Related
I am trying to take this code, split it into functions across my MVC website, and make it work with my existing database and mongoose implementation. I cloned the repo and that code all works.
So far I have adapted my database connection to this:
const connectDB = async () => {
try {
const conn = mongoose.connection;
// check connection
conn.on("error", (err) => {
console.error(`connection error: ${err.message}`);
});
conn.once("open", () => {
console.log("MongoDB Connected");
});
// init gfs
let gfs;
conn.once("open", () => {
// init stream
gfs = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: "assets",
});
});
// connection
await mongoose.connect(process.env.MONGO_URI);
return gfs;
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
I'm assuming it's okay to call mongoose.connect() multiple times as it returns a singleton, and won't create multiple connections. So I'm calling it in my controller to get access to my bucket, so I can call .find on it and get data out of it. Is there a better way?
The main issue is that my bucket exists in my controller file inside the gfs variable, since when I print it, it shows this:
[0] Promise {
[0] GridFSBucket {
[0] _events: [Object: null prototype] {},
[0] _eventsCount: 0,
[0] _maxListeners: 0,
[0] s: {
[0] db: [Db],
[0] options: [Object],
[0] _chunksCollection: [Collection],
[0] _filesCollection: [Collection],
[0] checkedIndexes: false,
[0] calledOpenUploadStream: false
[0] },
[0] [Symbol(kCapture)]: false
[0] }
[0] }
and yet when a few lines down, gfs.find() is called, an error is logged:
TypeError: gfs.find is not a function
I barely know what I'm doing, so any advice would be appreciated.
Thanks
------- update
My controller code is:
const gfs = require("../config/db").connectDB();
const uploadFile = (req, res) => {
return res.json({
message: "File uploaded successfully: " + req.file.filename,
});
};
const getMyFilenames = async (req, res) => {
console.log(gfs);
try {
const files = await gfs
.find({
//"metadata.uploader": req.user._id
})
.toArray();
if (!files || files.length === 0) {
return res.status(404).json({
message: "No files available",
});
}
const filenames = files.map((file) => file.filename.split("-")[1]);
console.log(filenames);
return res.json(filenames);
} catch (err) {
console.error(err);
}
};
u can put gfs to global
for ex
let gfs
const connectDB = async () => {
try {
const conn = mongoose.connection;
conn.on("error", (err) => {
console.error(`connection error: ${err.message}`);
});
conn.once("open", () => {
console.log("MongoDB Connected");
gfs = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: "assets",
});
});
await mongoose.connect(process.env.MONGO_URI);
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
const customF = () => {
if (!gfs) return;
console.log(gfs)
// gfs.find()
}
await connectDB()
setTimeout(()=>{
customf() // it was console.log(gfs) here
}, 10000) // i think 10s is enough to connect DB
As you saw when you printed gfs in the controller, connectDB is an async function which will return a promise, in this case resolving to the value of gfs defined in that function once it finishes executing. That TypeError is because find is not a method of a promise, so you need to await the promise first to unwrap it before calling .find.
There are multiple ways to do this, including:
Doing it inline - (await gfs).find… (and calling it something like gfsPromise could help make that clearer)
Importing the function instead, and calling it inline in place of the variable
Importing the function, and then initializing the variable in the controller with await, something like
const gfs = await connectDB();
…gfs.find(…
Not knowing the libraries, I’m not sure which you would need for your purpose around singletons and such.
Also to note, but connectDB could return/resolve the promise after mongoose.connect resolves but before the callback in your once open handler is run, and thus resolving the promise to undefined without waiting for gfs to actually be set. So you’ll likely want to actually construct the Promise to return, with the construction and resolve call lines starting something like:
const gfsPromise = new Promise(…
…resolve(new mongoose.mongo…
Hope that helps and gives you enough to go off of/make how you would want!
I'm new to node.js and async programming so I'm a bit confused on why this is happening. I am trying to run a discord bot which will give me data from a 3rd party website. I am able to query the data from the 3rd party site and I can see the data in my console. However the data is not able to show up in discord.
const { SlashCommandBuilder } = require('#discordjs/builders');
const execFile = require('child_process').execFile;
const path = require('node:path');
const commandsPath = path.join(__dirname, '..', 'Folder_name');
let scriptPath = path.join(commandsPath, 'querydata.js');
let output = "";
module.exports = {
data: new SlashCommandBuilder()
.setName('cmdname')
.setDescription('blank'),
async execute(interaction) {
await runScript(scriptPath)
await interaction.reply(output)
},
};
function runScript(scriptPath) {
return new Promise((resolve, reject) => {
execFile('node', [scriptPath], (error, stdout, stderr) => {
if (error) {
console.error('stderr', stderr);
throw error;
}
console.log(stdout);
output = stdout
resolve(output)
});
});
}
I have tried using Promise, async/await but I would either see this error
RangeError [MESSAGE_CONTENT_TYPE]: Message content must be a non-empty string.
Changing my code to the code below I would get this error instead, where I am able to query the data but by the time I gotten the data discord would terminate the call, thinking that my bot did not respond. (The bot is online when I tested this) enter image description here
module.exports = {
data: new SlashCommandBuilder()
.setName('cmd')
.setDescription('blank'),
async execute(interaction) {
runScript(scriptPath)
.then(interaction.reply(output))
},
};
function runScript(scriptPath) {
return new Promise((resolve, reject) => {
execFile('node', [scriptPath], (error, stdout, stderr) => {
if (error) {
console.error('stderr', stderr);
throw error;
}
console.log(stdout);
output = stdout
resolve(output)
});
});
}
I do not get it, I thought if I used await then the script would wait until the runScript function finishes before replying to discord. But the script keeps trying to send the empty output string to discord which causes the RangeError error.
On the other hand, when I use the second code block, I get the error in Discord(the error seen in the pic).
this should work for ya!
async execute(interaction) {
await runScript(scriptPath)
.then(output => interaction.reply(output))
}
it will await the script which literally means to wait for it to finish it's promises, then will grab the output from that and reply with it.
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
I have a method in a class that returns a Firebase user document:
class FirebaseUtils {
async getUserDocument(uid) {
if (!uid) return null
try {
const userDocument = await this.firestore.collection('users').doc(uid).get()
return { uid, ...userDocument.data() }
} catch (error) {
console.error('error getting user document: ', error)
}
}
}
I am getting Promise {<pending>} when I try to get the result of this function in another file
//need to update userDocument later
const userDocument = firebaseUtils.getUserDocument(uid)
console.log(userDocument) //Promise {<pending>}
I've tried this as well ascreating an immediatelly invoked function to await the getUserDocument function but that didn't work.
Since async functions return a Promise, you'll need to await or use .then to get the resolved value:
const userDocument = await firebaseUtils.getUserDocument(uid)
console.log(userDocument)
or
firebaseUtils.getUserDocument(uid).then((userDocument) => {
console.log(userDocument)
})
As a side note, you'll probably want to return null after logging the error in the catch, or at least be aware of the fact that the function returns undefined in that case.
I'm currently struggling to get variable values from one node.js module into another. This is my current problem:
I am fetching data from a REST API via https-request:
// customrequest.js
sendRequest( url, function( data, err ) {
if(err) {
console.log('--- Error ---');
console.log( err );
}
else {
console.log('--- Response ---');
console.log(data);
// output: data
return data;
}
module.exports = { sendRequest }
And my index.js file:
// index.js
let sendRequest = require('./customrequest');
let req;
req = sendRequest('google.com');
console.log(req);
// output: undefined
// how can I get the variable set, when request is getting data in response?
I totally understand, that the request to an API takes some time for the response. One solution is, that I just put everything into one js file. But as my project will get bigger over time, the modular approach is my goto-solution. Any suggestions on how to solve this?
Node uses callbacks for this situation. Try something like this:
// customrequest.js
sendRequest(url, callback)
module.exports = { sendRequest }
// index.js
let sendRequest = require('./customrequest');
let req = sendRequest('google.com', function (data, err) {
if (err) {
//handle error here
}
console.log(data);
};
// output: undefined
// how can I get the variable set, when request is getting data in response?
Thanks. The problem I encounter is somewhat different. I solved it with this code snippets … using async and await.
// request.js
const fetch = require('node-fetch')
async function myRequest (somestring) {
try {
let res = await fetch('https://api.domain.com/?endpoint='+somestring)
if (res.ok) {
if (res.ok) return res.json()
return new Error (res.status)
}
} catch (err) {
console.error('An error occurred', err)
}
}
module.exports = { myRequest }
// index.js
const request = require('./requests')
const myRequest = request.myRequest
let myVar;
myRequest('somestring')
.then(res => myVar = res.result)
setInterval(() => {
myRequest('somestring')
.then(res => myVar = res.result)
console.log(myVar)
}, 1000)
The async function and awaits return a promise. This promise is, when resolved, assigned to a variable.