I have this situation where I'm trying to send a request with the content of a file, but the problem is that the content arrives undefined. How can I solve this? I tried multiple versions from stackoverflow, but nothing worked so far.
const ifExists = (filePath) => {
try {
if (fs.existsSync(filePath)) {
return true;
}
} catch (err) {
console.log(err);
}
return false;
}
const readMyFile = async (filePath) => {
const fileExists = ifExists(filePath);
if (fileExists) {
fs.readFile(filePath, (err, data) => {
if (err) {
console.log("Error occurred when trying to read the file.");
return false;
}
console.log("File successfully read.");
return data; // data has the right content here
});
} else {
console.log("File not found");
return false;
}
}
const getFile = async function (req, res, next) {
try {
const content = await readMyFile(filePath); // the content is undefined here
res.writeHead(200, { "Content-Type": "application/json" });
res.write(JSON.stringify(content));
} catch (err) {
console.log("Error occurred.");
res.status(500).send("Error");
} finally {
res.end();
}
};
Thank you for your time!
fs.readFile uses a callback and does not return a promise which means it can't be used properly in an async function. If you want to use an async function I suggest returning a promise.
const readFile = async (filePath) => {
return new Promise((resolve, reject) => {
if (!exists(filePath)) {
reject(Error("File not found"));
}
fs.readFile(filePath, (err, data) => {
if (err) {
reject(err);
}
resolve(data)
});
})
}
Related
There is a mocha test as follows:
it('(t4) should assert that issues a GET request by john should fetch his own records via svc1', done => {
let filter = { where: { key: 'john' }};
let testHelperUrl = `${svc1}/api/TestHelper?filter=${encodeURIComponent(JSON.stringify(filter))}`;
let fetchToken = cb => {
req('GET', testHelperUrl, (err, resp) => {
if(err) {
return cb(err);
}
cb(null, resp.data[0]);
});
};
let getWithToken = ({ value: token }, cb) => {
let consumerUrl = `${svc1}/api/Consumers?access_token=${token}`;
req('GET', consumerUrl, (err, resp) => {
if(err) {
return cb(err);
}
cb(null, resp.data);
})
};
async.seq(fetchToken, getWithToken)((err, data) => {
if(err) {
return done(err);
}
expect(data.length).to.equal(2);
done();
});
});
It calls some APIs via the req(), and, it is defined as follows:
const req = (method, url, data, cb) => {
if (method.toLowerCase() === 'get') {
if (typeof cb === 'undefined') {
cb = data;
data = null;
}
(async () => {
try {
let response = await got(url, { responseType: 'json', https: { rejectUnauthorized: false } });
cb(null, { code: response.statusCode, data: response.body });
} catch (err) {
cb(err);
}
})();
}
else {
(async () => {
try {
let response = await got.post(url, { json: data, responseType: 'json', https: { rejectUnauthorized: false } });
cb(null, { code: response.statusCode, data: response.body });
} catch (err) {
cb(err);
}
})();
}
};
The above code uses got for doing http requests. Its design is promised-based, and hence, the reason to why async/await pattern is used here.
I am also using the async library here. The following line: expect(data.length).to.equal(2) should fail, and, the expectation is that mocha should report that error correctly.
However, the error is actually trapped by the async library. It should report an error saying callback already was called. But instead the program just dies. However, through debugging I am able to confirm that it was an error trapped in the async module. No idea why it dies abruptly though.
Am I doing something wrong? Or should I modify mocha code to properly handle the assertion error like wrapping that in a try/catch block or something?
Update:
Solution is to invoke the callback function to req() inside of a process.nextTick(). E.g.
try {
let response = await got(url, { responseType: 'json', https: { rejectUnauthorized: false } });
process.nextTick(() => cb(null, { code: response.statusCode, data: response.body }));
} catch (err) {
process.nextTick(() => cb(err));
}
I am trying to get the result back from function where the result is in the callback. However the result always come back as undefined even though I set it to be the res. Can someone explain why this does not work?
function getData(id, callback) {
return dataModel.findOne({ id }).exec((err, res) => {
if (err) return callback(err)
return callback(null, res)
})
}
async function middleware(req.body) {
try {
let result
await getData(req.body, (err, res) => {
if (err) throw err
result = res
})
await nextFunc(result)... // result is undefined
} catch (err) {
console.log(err)
}
}
You are using callbacks, but in order to make things work with async await you are supposed to use promises
Try
function getData(id, callback) {
return new Promise((resolve, reject) => {
dataModel.findOne({ id }).exec((err, res) => {
if (err) reject(err);
resolve(res);
});
});
}
async function middleware(req.body) {
try {
let result = await getData(req.body);
await nextFunc(result);
} catch (err) {
console.log(err);
}
}
Trying to write unit tests for reading a json file with the readFile function however I get the error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. I must be doing something wrong when mocking the json file.
The function:
function jsonReader(filePath, cb) {
fs.readFile(filePath, (err, fileData) => {
if (err) {
return cb && cb(err);
}
try {
const object = JSON.parse(fileData);
return object;
} catch (err) {
return cb && cb(err);
}
});
}
module.exports = jsonReader;
Then the testfile:
const jsonReader = require('.././ReadJson');
jest.mock('fs', () => {
const MOCK_FILE_INFO = { 'test.json': JSON.stringify({ name: 'myname' }) };
return {
readFile: (fpath, opts) => {
if (fpath in MOCK_FILE_INFO) {
return MOCK_FILE_INFO[fpath];
}
}
};
});
test('Test file', (done) => {
function callback(data) {
expect(data.name).toBe('myname');
done();
}
jsonReader('test.json', callback);
});
I tried to change the timeout but if I put it higher the execution also takes longer and it's still giving the same error.
You're trying to use your functions synchronously?
jest.mock('fs', () => {
const MOCK_FILE_INFO = { 'test.json': JSON.stringify({ name: 'myname' }) };
return {
readFile: (fpath, callback) => {
if (fpath in MOCK_FILE_INFO) {
callback(null, MOCK_FILE_INFO[fpath]);
}
}
};
});
function jsonReader(filePath, cb) {
fs.readFile(filePath, (err, fileData) => {
if (err) {
return cb && cb(err);
}
try {
const object = JSON.parse(fileData);
cb(object);
} catch (err) {
return cb && cb(err);
}
});
}
module.exports = jsonReader;
I am trying to perform a FTP request, wait till the file has downloaded and then close the FTP module. When both these actions have finished, then list the contents of a directory. At the moment, it is doing it in the opposite direction.
I have wrapped them in async and prepended the FTP with await. But the directory list is being logged first. Can spot the error in the async function?
(async function () {
await Ftp.get("document.txt", "document.txt", err => {
if (err) {
return console.error("There was an error retrieving the file.");
}
console.log("File copied successfully!");
Ftp.raw("quit", (err, data) => {
if (err) {
return console.error(err);
}
console.log("Bye!");
});
});
})()
// Read the content from the /tmp directory to check it's empty
fs.readdir("/", function (err, data) {
if (err) {
return console.error("There was an error listing the /tmp/ contents.");
}
console.log('Contents of tmp file above, after unlinking: ', data);
});
First, await only works with promises, and ftp.get apparently uses a callback instead of a promise. So you'll have to wrap ftp.get in a promise.
Second, your fs.readdir is outside of the async function, so it is not going to be affected by the await. If you need it to be delayed, then you need it to be inside the async function, after the await statement.
So put together that looks something like this:
(async function () {
await new Promise((resolve, reject) => {
Ftp.get("document.txt", "document.txt", err => {
if (err) {
reject("There was an error retrieving the file.")
return;
}
console.log("File copied successfully!");
Ftp.raw("quit", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
})
});
fs.readdir("/", function (err, data) {
if (err) {
return console.error("There was an error listing the /tmp/ contents.");
}
console.log('Contents of tmp file above, after unlinking: ', data);
});
})()
I usually try to separate things out. It looks like you wanted to save a file so I built this out with that in mind. I put each request into it's own promise. I don't think you need Ftp.raw. I am not sure if Ftp is the node library or simply the var name of another library.
const util = require("util");
const fs = require("fs");
const fsOpen = util.promisify(fs.open);
const fsWriteFile = util.promisify(fs.writeFile);
const fsClose = util.promisify(fs.close);
async function saveDocumentAndListDirectoryFiles() {
let documentData;
let fileToCreate;
let listDirectoryFiles
//We get the document
try {
documentData = await getDocument();
} catch (error) {
console.log(error);
return;
}
// Open the file for writing
try {
fileToCreate = await fsOpen("./document.txt", "wx");
} catch (err) {
reject("Could not create new file, it may already exist");
return;
}
// Write the new data to the file
try {
await fsWriteFile(fileToCreate, documentData);
} catch (err) {
reject("Error writing to new file");
return;
}
// Close the file
try {
await fsClose(fileToCreate);
} catch (err) {
reject("Error closing new file");
return;
}
// List all files in a given directory
try {
listDirectoryFiles = await listFiles("/");
} catch (error) {
console.log("Error: No files could be found");
return;
}
console.log(
"Contents of tmp file above, after unlinking: ",
listDirectoryFiles
);
};
// Get a document
function getDocument() {
return new Promise(async function(resolve, reject) {
try {
await Ftp.get("document.txt", "document.txt");
resolve();
} catch (err) {
reject("There was an error retrieving the file.");
return;
}
});
};
// List all the items in a directory
function listFiles(dir) {
return new Promise(async function(resolve, reject) {
try {
await fs.readdir(dir, function(err, data) {
resolve(data);
});
} catch (err) {
reject("Unable to locate any files");
return;
}
});
};
saveDocumentAndListDirectoryFiles();
I would like to make an Ajax request to my MongoDB server and use the data along with other async tasks using a standalone function so that I can modularize my code as much as possible. I am not very experienced with async programming so I might be doing some basic mistake. In my code, I used another function (doubleAfter2Seconds) returning a promise, which works fine. The result variable from await getMongoData("mydb", url) is outputted as undefined instead of the actual data.
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://{MyServer}:27017/";
function getMongoData(dboArg, urlArg) {
var myPromise = () => {
return new Promise(resolve => {
MongoClient.connect(urlArg, { useNewUrlParser: true }, function(err, db) {
if (err) throw err;
var dbo = db.db(dboArg);
dbo.collection(myCollection).find({}).toArray(function(err, result) {
if (err) throw err;
console.log(result);
db.close();
resolve(result[0]);
});
})
})
}
}
function doubleAfter2Seconds(x) {
return new Promise(resolve => {
console.log("v");
setTimeout(() => {
resolve(x * 2);
}, 1000);
});
}
async function addAsync(x) {
// This works
const a = await doubleAfter2Seconds(10);
console.log(a);
// This doesn't work
result = await getMongoData("mydb", url);
console.log(result);
return x;
}
addAsync(10).then((sum) => {
console.log(sum);
});
Here is the corrected getMongoData function based on the comments
function getMongoData(dboArg, urlArg) {
return new Promise(resolve => {
MongoClient.connect(urlArg, { useNewUrlParser: true }, function(err, db) {
if (err) throw err;
var dbo = db.db(dboArg);
dbo.collection(myCollection).find({}).toArray(function(err, data) {
err
? reject(err)
: resolve(data[0]);
});
})
})
}