How to return promise from fs.unlink - javascript

I want to delete a file and wait for the deletion to succeed before moving forward. I have used unlink function inside a promise to get the result, but when unlink done successfully then I am getting the result from the promise if there is any kink of error while deleting the file the promise does not return any error.
Service:
public removeUserImage(
user: User,
): Promise<NodeJS.ErrnoException | boolean> {
const pathToRemoveImage = 'src/public/uploads'+ '/' + user.image_url;
return new Promise((resolve, reject) => {
unlink(pathToRemoveImage, (error) => {
if (error) reject(error);
resolve(true);
});
});
}
Controller:
const isFileRemoved = await this._userService.removeUserImage(user);
//This block not excuting
if (!isFileRemoved) {
throw new InternalServerErrorException(
'Error occurred while trying to remove file.',
);
}

Your promise rejects if there's an error. When using await, you need to wrap the code in try..catch in order to handle any failures
try {
await this._userService.removeUserImage(user);
} catch (err) {
console.error(err);
throw new InternalServerErrorException(
'Error occurred while trying to remove file.'
);
}
FYI, you can (and should) use the Promises API versions of the fs functions
import { unlink } from "node:fs/promises";
public removeUserImage({ image_url }: User): Promise<void> {
const pathToRemoveImage = `src/public/uploads/${image_url}`;
return unlink(pathToRemoveImage);
}
If you wanted your method to always resolve with a Boolean, you'd want something like
return unlink(pathToRemoveImage)
.then(() => true) // resolve with "true" for success
.catch((err) => {
console.error("removeUserImage", image_url, err);
return false; // resolve with "false" for failure
});

The error will always go to catch block,
try {
await this._userService.removeUserImage(user);
} catch (err) {
console.error(err);
throw new InternalServerErrorException(
'Error occurred while trying to remove file.'
);
}
Suggestion: You don't need to convert unlink(callback) to promise fs has promise function also, check this
const fs = require('fs');
const fsPromises = fs.promises;
public removeUserImage(
user: User,
): Promise<void> {
const pathToRemoveImage = 'src/public/uploads'+ '/' + user.image_url;
return fsPromises.unlink(pathToRemoveImage);
}

Related

How to call an API twice if there is an error occurred?

I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)

resolve and reject issue using node js

Is this possible way to return resolve or reject message from one function to another?
As I am writing to pass resolve message in postman whenever my task is completed or reject message when there is some error
But after after writing return it still not returning the resolve message or reject message inside Postman
any idea how this can be resolve?
async function readFile(filePath) {}
async function getAllFile(filePath) {
const paths = await readFile(filePath);
}
async function filterFiles(filePath) {
const paths = await getAllFile(filePath);
}
function addDocument(childProduct){
return new Promise((resolve, reject) => {
Document.create({
name: childProduct,
},
}).then(function (filePath) {
filterFiles(filePath);
let msg = "Document created Succesfully";
return resolve(msg);
})
.catch(function (err) {
return reject("Can't be updated please try again :) " + err);
});
});
}
function updateDoc(data){
return new Promise((resolve, reject) => {
Document.update({
name: data.name,
}
where: {
product_id: data,
},
})
}).then(function (childProduct) {
addDocument(childProduct);
let msg = "Updated Successfully";
return resolve(msg);
})
.catch(function (err) {
return reject("Can't be updated please try again :) " + err);
});
}
Product.findOne and Document.findAll return a Promise, so they can be returned and awaited directly.
You can chain await func1(); await func2(); await func3() in one try{} block, and catch any error that happens in one place :
const filterFiles = async filePath => {
const paths = await getAllFiles(filePath);
// .. Do something else here
return paths // This is a Promise because async functions always return a Promise
}
const findOneDoc = name => Product.findOne({ where: { name } }); // This func returns a Promise
const findAllDocs = product_id => Document.findAll({ // This func returns a Promise too
raw: true,
where: { product_id }
});
(async () => {
try {
const childProduct = await findOneDoc("some_name");
console.log("All good until now!");
const filePath = await findAllDocs(childProduct._id);
console.log("Still good");
const filteredFiles = await filterFiles(filePath);
console.log("All went well.");
console.log(filteredFiles);
} catch (err) {
// If any of the functions above fails, the try{} block will break and the error will be caught here.
console.log(`Error!`, err);
}
})();
There are few things I would like to mention.
When you create a promise, it should have resolve() and reject() inside it.
for ex-
function testPromise() {
return new Promise((resolve, reject) => {
// your logic
// The followin if-else is not nessesary, its just for an illustration
if (Success condition met) {
resolve(object you want to return);
}else {
reject(error);
// you can add error message in this error as well
}
});
}
// Calling the method with await
let obj = await testPromise()
// OR call with then, but its better to go with await
testPromise().then((obj)=>{
// Access obj here
})
In the method which you have written, You have applied .then() method to non promise object. You have to complete the promise block first with resolve() and reject() inside it. Then you can return that promise from a function, use it in async function Or apply .then() block on it.
Also you don't need to add return statement to resolve() and reject() statement. The system will take care of it.
You can also use try catch block inside a promise. Its better to write reject() statement in catch block, if anything goes wrong.

nodejs promises giving Unhandled Promise Rejection which seems to be handled properly

The problem is that i am getting UNhandledPromiseRejection error eveen though i think i have handled all the cases. The code flows from profileRoutes to Controller to Utils where the error comes first.
Inside the profileRoutes.js
router.get('/:username', async (r, s) => {
try{
let profileData = await getProfileData(r.params.username);
s.json({ success: true, payload: profileData });
}catch(err){
console.log('ending request processing by responding a error');
s.status(500).json({ success: false, message: 'err[0].message' });
}
});
Inside the controllers/index.js
const fetchQueue = [getUserRepos];
async function getProfileData(username) {
let profileData = {};
try{
let results = await Promise.all(fetchQueue.map(item => item(username)));
for (let i = 0; i < results.length; i++) {
profileData[getKeys[i]] = results[i];
}
return profileData;
}catch(err){
console.log('error log in controller/index getProfileData function');
throw err;
}
}
const getUserRepos = async (username) => {
try {
// const res = await utils.gqlSender(username, 'userRepos', { createdAt });
const res = await utils.gqlSender(username, 'userReposData');
return res.user.repositories;
} catch (err) {
console.log('error log in controller/index getUserRepos function');
throw err;
}
};
Inside the utils/index.js
const gqlSender = async (username, type, opt = {}) => {
axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if(res.data.errors) { // this is where the error is recieved and so i reject promise.
console.log('bef###re');
return Promise.reject (res.data.errors);
}
console.log('###',res.data);
return res.data;
}).catch(err => {
console.log('error in making axios request inside utils/index gqlSender function');
throw err;
// return Promise.reject(err);
});
The stack trace on making get request to /:username is-
error log in controller/index getUserRepos function
error log in controller/index getProfileData function
ending request processing by responding a error
bef###re
error in making axios request inside utils/index gqlSender function
(node:11260) UnhandledPromiseRejectionWarning: [object Array]
(node:11260) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:11260) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I dont think i am missing any Promise Rejection.
Any help is appreciated. Thanks.
i have referred these answers previously -
What's the difference between returning value or Promise.resolve from then()
Do I need to return after early resolve/reject?
Your gqlSender function is not returning the promise that will get rejected, so it is not handled anywhere. You should write either
const gqlSender = (username, type, opt = {}) => {
return axios.post('', {
// ^^^^^^
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
});
};
or
const gqlSender = async (username, type, opt = {}) => {
// ^^^^^
const res = await axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
});
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
}

Adding a catch block to a promise returns pending instead of rejected

I have a working apollo graphql express server. The only problem is express is complaining that I don't have a catch block on a promise I'm using to verify a jwt token:
(node:96074) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
I can add a catch block to the promise but it then returns pending instead of rejected when the token is invalidated. Which causes the authentication flow to break as my graphql resolvers rely on that rejection to block access to the db.
Fwiw this is how auth0, who I'm using for auth, recommends setting it up. They just don't mention the UnhandledPromiseRejectionWarning.
The code looks like this:
//server def
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
//THE PROMISE IN QUESTION
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
reject(err);
}
if (decoded) {
resolve(decoded);
}
});
});
return {
authUserObj
};
}
},
introspection: true,
playground: true
});
//a graphql resolver that gets the rejection via authUserObj and catches the error
addUser: async (parent, args, {authUserObj}) => {
try {
const AuthUser = await authUserObj;
const response = await User.create(args);
return response;
} catch(err) {
throw new AuthenticationError('You must be logged in to do this');
}
}
That all works... except for that nagging node error I wish to vanquish! So I add a catch block to the promise:
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
console.log("-------rejected-------", err.message)
reject(err);
}
if (decoded) {
console.log("-------decoded-------")
resolve(decoded);
}
});
}).catch( err => { return err.message});
And now instead of authUserObj returning rejected it's pending and anyone will be able to add a user, which kind of defeats the purpose of auth.
If anyone knows how to catch that error while still rejecting it I'm all ears. Thanks.
The problem is less about the unhandled promise rejection and more about the unhandled promise in general. You try to put a promise inside the context object and then await the promise in the addUser resolver only. In other resolvers, the promise might not be used at all, and when the jwt verification fails for those the rejection will go unhandled. (Also if the resolvers are executed asynchronously, the promise might be rejected before they can handle it).
Instead, the whole context initialisation should be done asynchronously, returning a promise for the context object with the user details. This means that the request will fail before even starting to execute a query:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
}).then(authUser => {
if (authUser) return { authUser };
// else return {};
});
// .catch(err => { … }) - you may chose to ignore verification failure,
// and still return a context object (without an `authUser`)
}
// else return {}; - when not sending the header, no token will be checked at all
},
introspection: true,
playground: true
});
// a graphql resolver that checks for the authUserObj
addUser: async (parent, args, {authUserObj}) => {
if (!authUserObj) { // you might also want to check specific claims of the jwt
throw new AuthenticationError('You must be logged in to do this');
}
const response = await User.create(args);
return response;
}
Just like with try/catch, a .catch() will change the promise chain from rejected to resolved if you just return a normal value from the .catch() handler (or return nothing). When you return a "normal" value, the rejection is considered "handled" and the promise chain becomes resolved with that new value. That's how you handle errors and continue normal processing.
To keep the promise chain rejected, you have to either throw or return a rejected promise. That will keep the promise chain as rejected.
So, if you want authUserObj to keep the promise rejected, then change this:
}).catch( err => { return err.message});
to this:
}).catch( err => { return Promise.reject(err.message)});
or something similar that either throws an error or returns a rejected promise.

Propagate rejected promise via a .catch()

I have some code which essentially looks like this:
export function firstFunction(req: express.Request, res: express.Response, next: express.NextFunction): void {
secondFunction(id)
.then((userId: UserId) => {
res.status(200).send(UserId);
})
.catch((err) => {
if (err instanceof NoResultError) {
res.status(404).send(err);
} else {
next(err);
}
});
}
export function secondFunction(id: string): Promise<UserId> {
return new Promise<UserId>((resolve, reject) => {
thirdFunction(id)
.then((data: TableInfo) => {
if (Object.keys(data).length !== 3) {
reject(new Error('data in database is not mapped properly'));
}
resolve(data);
})
.catch((err) => {
// WANT TO PROPAGATE ERROR UP TO THE GETDETAILS FUNCTION WHICH CALLS THIS
});
});
}
export function thirdFunction(id: string): Promise<TableInfo> {
return new Promise<TableInfo>((resolve, reject) => {
let query = `
//query goes here
`;
db.executeQuery(query, [id])
.then((data: TableInfo) => {
if (Object.keys(data).length < 1) {
reject(new NoResultError('some message here'));
}
resolve(data);
});
});
}
My goal is to have the lowest level of the three functions (thirdFunction) determine if the data from the db-query finds no data and then reject that promise with an error. Then the secondFunction should ideally catch this error and propagate it up to firstFunction so that firstFunction can handle that error properly. I have tried doing a throw err a return err and a return Promise.reject(err) all of which lead to an unhandled promise rejection. What is my (probably fundamental) misunderstanding of how this should work?
the secondFunction should ideally catch this error and propagate it up
No, propagation is the default. Ideally you should not need to catch it at all, and it will propagate up automatically.
I have tried things that all lead to an unhandled promise rejection. What is my (probably fundamental) misunderstanding?
You're using the Promise constructor antipattern! With it, you would need to call reject(err) in the catch handler to make it work. Or really .then(resolve, reject);. But that's absolutely not how this should be done.
Instead, drop the new Promise wrapper and just return the result of chaining then handlers:
export function secondFunction(id: string): Promise<UserId> {
return thirdFunction(id)
.then((data: TableInfo) => {
if (Object.keys(data).length !== 3) {
throw new Error('data in database is not mapped properly');
}
return getUserId(data);
});
}
export function thirdFunction(id: string): Promise<TableInfo> {
let query = `/* query goes here */`;
return db.executeQuery(query, [id])
.then((data: TableInfo) => {
if (Object.keys(data).length < 1) {
throw new NoResultError('some message here');
}
return data;
});
}

Categories

Resources