Returning promise from async function - javascript

Can promise to be returned like below? Does the catch block return a promise or a normal Error?
async function beforeProcessing(ctx) {
try {
let res = await validateList(ctx)
return new Promise((resolve, reject) => {
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
reject(customErr);
}
else {
resolve();
}
})
} catch (error) {
return new Error(error);
}
}

Yes, but there's no reason to. async functions always return promises. Just handle that logic inline, and throw customErr to reject the function's promise:
async function beforeProcessing(ctx) {
let res;
try {
res = await validateList(ctx)
} catch (error) {
return new Error(error); // *** This is highly suspect
}
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
throw customErr;
}
}
But as I mentioned in a comment above, returning an error object is highly suspect, it fulfills the promise with the error rather than rejecting the promise. Just let the error propagate to the caller (which will reject the async function's promise):
async function beforeProcessing(ctx) {
let res = await validateList(ctx)
if (res && res.length > 0) {
const customErr = new Error(`missing for ${res} types`);
customErr.statusCode = 422;
throw customErr;
}
}
Also, that if condition looks odd vs. the error message. The error message makes it look like the condition should be <= 0, not > 0, but of course I could be inferring incorrectly there.

Related

How can I use async/await in 'new Promise' block to fetch data from an api?

I'm unable to use async/await inside a 'new Promise' block which throws await is only valid in async function error. I don't know if it's even possible but I need to use it or a way to achieve what I want. Here's what I'm trying to do:
A user types in a query and it goes through a recursive function called doesExist. I have to use a recursive function because the API doesn't always provide data for a query. The recursive function will try 3 times, that is, it will send 3 api requests for the data before returning an error saying 'data couldn't be fetched'. In the function, I return a new promise as I have to make api requests from there.
To fetch data, I used request before but I want to use axios now with async/await. So how can I use axios with async/await inside a new Promise block?
This is the code with request:
router.get('/example', async (req, res) => {
try{
const query = 'Superman';
const data = await doesExist(query);
if(!data) {
console.log('No data');
}
res.render('../views/example', { data, query });
}catch(err) {
console.log(err);
}
});
const doesExist = (query, retries = 0) => {
const url = `http://api.example.com/json?fields=${query}`;
const maxRetries = 3;
return new Promise(( resolve, reject ) => {
const retry = () => {
if (retries < maxRetries) {
resolve(doesExist(query, retries + 1));
} else {
reject(`Could not get the data after ${retries} retries.`);
}
};
request(url, function (error, response, body) {
if (!error && response.statusCode === 200) {
const data = JSON.parse(body);
resolve(data);
} else {
retry();
}
});
});
};
And this is what I tried with async/await which throws the error:
const doesExist = async (query, retries = 0) => {
const url = `http://api.example.com/json?fields=${query}`;
const maxRetries = 3;
return new Promise(( resolve, reject ) => {
const retry = () => {
if (retries < maxRetries) {
resolve(doesExist(query, retries + 1));
} else {
reject(`Could not get the data after ${retries} retries.`);
}
};
const data = await axios.get(url);
if(data.statusCode === 200) {
resolve(data);
}else{
retry();
}
});
};
If it's not possible to use async/await inside promise block then please show me how I can achieve it. Thanks
Never pass an async function as the executor to new Promise! If you want to use async/await and axios which already does return a promise, you don't need - and shouldn't use - the Promise constructor to promisify a callback API. Just write
async function doesExist(query, retries = 0) {
const url = `http://api.example.com/json?fields=${query}`;
const maxRetries = 3;
const data = await axios.get(url);
if (data.statusCode === 200) {
return data;
} else if (retries < maxRetries) {
return doesExist(query, retries + 1);
} else {
throw new Error(`Could not get the data after ${retries} retries.`);
}
}
The function the promise calls needs to be an "async" function like so.
new Promise (async (Resolve) => {
await new Promise (async (_Resolve) => {
console.log ("A");
_Resolve ();
});
console.log ("B");
Resolve ();
});

async await not waiting for resolve

So, im my nodeJS project I have this part of my code, that is not waiting for the await call
Controller.js
exports.getList = async function(request, response) {
try {
const result = await useCase.getList()
if (result == undefined) {
utils.unavailable(response)
}
else if (result == null || result.length == 0) {
utils.respond(response, 404, errCodes.NOT_FOUND)
}
else utils.respond(response, 200, result)
}
catch(e) {
utils.unavailable(response)
}}
This is the function on the controller, it calls the response from the useCase
UseCase.js
exports.getList = async function() {
return await new Promise(function(resolve) {
resolve(repository.getList())
})
Which also calls the repository
Repository.js
exports.getList = async function() {
MongoClient.connect(Constants.DB_URL, function(err, db) {
if (err) {
console.log(err)
return undefined
}
const dbo = db.db(Constants.DB_NAME)
dbo.collection(Constants.DB_FEATURE1_COLLECTION).find({}).toArray(function(err, result) {
if (err) {
console.log(err)
return undefined
}
db.close()
return result
})
});
So, what happens is that on the controller, the result is always undefined, since result hasn't been initialized, when it should have actually waited for the response of the Promise on the UseCase. So what am I doing wrong?
getList function does not have return statement, and since it is async it returns a Promise that always resolves with undefined. I am not very familiar with MongoClient, but documentation says it returns a promise if no callback is specified. So you can change your code:
exports.getList = async function () {
try {
const db = await MongoClient.connect(Constants.DB_URL);
const dbo = db.db(Constants.DB_NAME);
const result = await dbo.collection(Constants.DB_FEATURE1_COLLECTION).find({}).toArray();
db.close();
return result
} catch (err) {
console.log(err);
return undefined
}
};
and UseCase.js:
exports.getList = function() {
return repository.getList(); // it returns a Promise there is no need to wrap it in async
};

How can i rewrite these JavaScript promises to be less complicated?

I wrote this, but it looks rough, its for node js so all values need to be gotten in order
import tripfind from '../model/tripfind';
import addbook from '../model/addbook';
import bookfind from '../model/bookfind';
function addBook(info) {
return new Promise((resolve, reject) => {
let rowcount = -1;
bookfind(0, -1, -1).then((result) => {
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
tripfind(info.trip_id, 0).then((xresult) => {
let val = '';
if (xresult === 'false') {
val = 'trip is not valid';
resolve(val);
} else {
addbook(info, rowcount).then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}).catch((err) => {
if (err) {
reject(err);
}
});
}
}).catch((error) => {
reject(error);
});
}).catch((err) => {
if (err) {
reject(err);
}
});
});
}
export default addBook;
thats the code above, the code also gets exported and is handled as a promise function, please help if you can
Without using async / await, this example is functionally equivalent to what you had before:
function addBook (info) {
return bookfind(0, -1, -1).then(result => {
const { rowCount } = result || { rowCount: 0 };
return tripfind(info.trip_id, 0).then(trip =>
trip === 'false'
? 'trip is not valid'
: addbook(info, rowCount)
);
});
}
Sections like
.then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
})
.catch((err) => {
if (err) {
reject(err);
}
});
.catch((error) => {
reject(error);
});
are completely superfluous, so removing them doesn't affect the logic at all. After removing those, and unwrapping your Promise constructor antipattern, you can see the logic becomes a lot more simplified.
You're missing some returns of promises, and if you like this way of writing you should indent then() and catch() in the same way. Also doing this
.catch((err) => {
if (err) {
reject(err);
}
});
is useless, caus you catch an error to reject it. Rewritten in promise pattern (and a bit of optimization):
function addBook(info) {
return bookfind(0, -1, -1)
.then((result) => {
rowcount = result ? result.rowCount : 0
return tripfind(info.trip_id, 0)
.then((xresult) => {
if (xresult === 'false') {
return 'trip is not valid'
}
return addbook(info, rowcount)
})
})
}
Anyway is more clear with the async/await pattern:
async function addBook(info) {
let result = await bookfind(0, -1, -1)
rowcount = result ? result.rowCount : 0
let xresult = await tripfind(info.trip_id, 0)
if (xresult === 'false')
return 'trip is not valid'
let value = await addbook(info, rowcount)
return value
}
like this?
function addBook(info) {
return bookfind(0, -1, -1).then((result) => tripfind(info.trip_id, 0)
.then((xresult) => (xresult === 'false') ?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0)
)
);
}
or with async/await
async function addBook(info) {
const result = await bookfind(0, -1, -1);
const xresult = await tripfind(info.trip_id, 0)
return xresult === "false"?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0);
}
Avoid the Promise/Deferred antipattern: What is the explicit promise construction antipattern and how do I avoid it?
and remove pointless/useless code, like this function for example:
(value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}
which always resolves to value. that's like return value === 1? 1: value
Or all the (error) => { reject(error); } just to "forward" the error to the returned promise.
or this construct
let rowcount = -1;
//....
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
//....
doSomethingWith(rowcount)
which can be summed up as doSomethingWith(result ? result.rowCount : 0)
A lot of people might tell you that async/await will solve this better. I think the problem is more about how the promises are used. If you have access to change the functions you are calling here's what I'd recommend.
Have functions return things of the same shape - e.g., the call to bookfind can return a rejected promise or a resolved promise that wraps undefined or {..., :rowcount:12}. If we remove the undefined result and return a default {..., rowcount:0 } it would simplify the calling functions substantially and contain logic a bit better.
Remove the superfluous function calls (.catchs and the addbook .then bit)
Here's what those functions could look like (not knowing your setup at all)
function tripfind(id, num) {
return new Promise(function(resolve, reject) {
doSomething(function(err, results) {
if (err) return reject(err);
if (results === "false") return reject(new Error("trip is not valid"));
return resolve(results);
});
});
}
function bookfind(id, start, end /*or whatever*/) {
return new Promise(function(resolve, reject) {
findBooks(function(err, results) {
if (err) return reject(err);
// handle odd cases too
if (!results) return resolve({ rowcount: 0 });
return resolve(results);
});
});
}
and the usages
bookfind(0, -1, -1).then(results =>{}).catch(err=>{})
tripfind(id, 0).then(results =>{}).catch(err=>{})
Using Promises this way, we don't have to check the value of results because if it wasn't there, the promise shouldn't resolve. Likewise, the shape of the thing coming out of the promise should always be the same. (I call it a smell when a function returns String|Object|Undefined, it's possible but I'd look hard at it first)
Using this pattern and removing calls that don't do anything (all the .catch calls), we can simplify the code down to:
function addBook(info) {
return bookfind(0, -1, -1).then(({ rowcount }) =>
tripfind(info.trip_id, 0).then(() =>
addbook(info, rowcount)
)
);
}

await for indexdb event in async function

I'm trying to return a custom object from a async function that works as wrapper for a put using indexdb.
Using Promises this is easy.
However, using async/await became more challenging...
const set = async (storeName, key, value) => {
if (!db)
throw new Error("no db!");
try {
const result = {};
let tx = db.transaction(storeName, "readwrite");
let store = tx.objectStore(storeName);
let r = store.put({ data: key, value: value });
console.log(r);
r.onsuccess = async () => {
console.log('onsuccess');
result.something = true;
}
r.onerror = async () => {
console.log('onerror');
result.something = false;
}
await r.transaction.complete; // ok... this don't work
// how can I await until onsuccess or onerror runs?
return result;
} catch (error) {
console.log(error);
}
}
The ideia is to return a composed object... however all my attemps fails as onsuccess runs after returning the result.
I googled a lot and could't find a way to proper await for onsuccess/onerror events.
I know that returning a Promise is more easy as resolve(result) would end returning what I want... but i'm trying to learn to make same code using async/await.
Thank you so much,
Try this:
function set(db, storeName, key, value) {
return new Promise((resolve, reject) => {
let result;
const tx = db.transaction(storeName, 'readwrite');
tx.oncomplete = _ => resolve(result);
tx.onerror = event => reject(event.target.error);
const store = tx.objectStore(storeName);
const request = store.put({data: key, value: value});
request.onsuccess = _ => result = request.result;
});
}
async function callIt() {
const db = ...;
const result = await set(db, storeName, key, value);
console.log(result);
}
Edit, since you insist on using the async qualifier for the set function, you can do this instead. Please note I find this pretty silly:
async function set(db, storeName, key, value) {
// Wrap the code that uses indexedDB in a promise because that is
// the only way to use indexedDB together with promises and
// async/await syntax. Note this syntax is much less preferred than
// using the promise-returning function pattern I used in the previous
// section of this answer.
const promise = new Promise((resolve, reject) => {
let result;
const tx = db.transaction(storeName, 'readwrite');
tx.oncomplete = _ => resolve(result);
tx.onerror = event => reject(event.target.error);
const store = tx.objectStore(storeName);
const request = store.put({data: key, value: value});
request.onsuccess = _ => result = request.result;
});
// We have executed the promise, but have not awaited it yet. So now we
// await it. We can use try/catch here too, if we want, because the
// await will translate the promise rejection into an exception. Of course,
// this is also rather silly because we are doing the same thing as just
// allowing an uncaught exception to exit the function early.
let result;
try {
result = await promise;
} catch(error) {
console.log(error);
return;
}
// Now do something with the result
console.debug('The result is', result);
}
Ultimately you'll end up wrapping IDB in a promise-friend library, but for your specific need, you could use something like this:
function promiseForTransaction(tx) {
return new Promise((resolve, reject) => {
tx.oncomplete = e => resolve();
tx.onabort = e => reject(tx.error);
});
}
And then in your code you can write things such as:
await promiseForTransaction(r.tx);
... which will wait until the transaction completes, and throw an exception if it aborts. (Note that this requires calling the helper
before the transaction could possibly have completed/aborted, since
it won't ever resolve if the events have already fired)
I can't confirm it right now but I think it should be await tx.complete instead of await r.transaction.complete;.
But a general solution that would work even if the API would not support Promises directly would be to wrap a new Promise around the onsuccess and onerror and use await to wait for that Promise to resolve, and in your onsuccess and onerror you then call the resolve function:
const set = async (storeName, key, value) => {
if (!db)
throw new Error("no db!");
try {
const result = {};
let tx = db.transaction(storeName, "readwrite");
let store = tx.objectStore(storeName);
let r = store.put({
data: key,
value: value
});
console.log(r);
await new Promise((resolve, reject) => {
r.onsuccess = () => {
console.log('onsuccess');
result.something = true;
resolve()
}
r.onerror = () => {
console.log('onerror');
result.something = false;
// I assume you want to resolve the promise even if you get an error
resolve()
}
})
return result;
} catch (error) {
console.log(error);
}
}
I would furhter change it to:
try {
await new Promise((resolve, reject) => {
r.onsuccess = resolve
r.onerror = reject
})
console.log('success');
result.something = true;
} catch(err) {
console.log('error');
result.something = false;
}

Rejecting Promise if many error sources

Im in situation where I have many potential error sources. Is there an elegant solution to this mess?
How should I reject it?
function myFuction(hash) {
return new Promise((resolve, reject) => {
// this could return error
const id = atob(hash);
// this could return error
let data = firstFunction(id);
// return error if not true
if (data && data.id) {
// this could return error
return secondFunction(data.id)
.then(item => {
// return error if not true
if (item) {
// this could return error
return thirdFunction(item)
.then(payload => {
resolve('OK');
});
}
});
}
});
}
Avoid the Promise constructor antipattern! You can use early returns with Promise.reject or just throw errors:
function myFuction(hash) {
return Promise.resolve().then(() => {
// this could throw error
const id = atob(hash);
// this could throw error
let data = firstFunction(id);
// return error if not true
if (!data || !data.id)
return Promise.reject(new Error("…")); // alternative: throw new Error("…");
return secondFunction(data.id);
}).then(item => {
// return error if not true
if (!item)
return Promise.reject(new Error("…")); // same here
return thirdFunction(item);
}).then(payload => 'OK');
}
(Additionally I applied some flattening, but as long as your always return from promise callbacks you could nest as well)

Categories

Resources