gracefull fallback where one promise in async / await chain fails? - javascript

So, I've got a chain of api calls that I need to perform, something like this
const myBigCall = async (paramsObj) => {
try {
const data1 = await myCall1(paramsObj.first);
const data2 = await myCall2(paramsObj.second);
const data3 = await myCall3(paramsObj.third);
} catch e { console.error(e) }
}
in this case data1, data2, data3 are unrelated, yet have to be done within 1 function like this. This means if one of these requests fails, application will be partially erred, but still can show other data. Right now if data1 or data2 fail data3 will never be called. Is there a way to call it, yet still be able to catch errors?

If the calls are unrelated, you shouldn't use await that way as it will block every call. At the very least, you should use Promise.all to make the calls simultaneously.
const myBigCall = async (paramsObj) => {
try {
const dataArray = await Promise.all([
myCall1(paramsObj.first),
myCall2(paramsObj.second),
myCall3(paramsObj.third)
]);
} catch e { console.error(e) }
};
However, that will still fail if one of the promises fail. An easy way to get out of it would be to create a wrapper function that will prevent the fail and return null or undefined in case of fail and remove the whole try block:
const dontFail = promise => promise.catch(() => null);
const myBigCall = async (paramsObj) => {
const dataArray = await Promise.all([
myCall1(paramsObj.first),
myCall2(paramsObj.second),
myCall3(paramsObj.third)
].map(dontFail));
}

Related

How to return a promise that resolves customized data from API Javascript

So I'm using the API of National Weather Service to create a weather app. But the fetched data is very complicated, and I need to fetch two different APIs. So I want to write a customized async function that returns a promise, which resolves to an object that contains only the necessary data I need.
I came up with something like this:
async function fetchWeatherAPI(){
//data that I need
let data = {};
try {
const res1 = await fetch(url1);
const result1 = await res1.json();
data = {...data , result1.usefulData};
} catch(error){}
try {
const res2 = await fetch(url2);
const result2 = await res2.json();
data = {...data, result2.usefulData};
} catch(error){}
return new Promise((resolve,reject)=>{
resolve(data);
})
}
This code is working for me. But the problem is, what if the APIs reject? How can I handle the error so that I can display the error message in the returned promise? I may want something like this:
return new Promise((resolve,reject)=>{
if(...) reject(errrorMessage);
resolve(data);
})
Just do return data. You're already inside an async function and the return value from that function will be the resolved value of the promise that the async function already returns:
async function fetchWeatherAPI(){
//data that I need
let data = {};
const res1 = await fetch(url1);
const result1 = await res1.json();
data = {...data , result1.usefulData};
const res2 = await fetch(url2);
const result2 = await res2.json();
data = {...data, result2.usefulData};
return data;
}
In addition, your catch blocks are silently eating errors and attempting to continue as if nothing went wrong. You probably just want the error to propagate back to the caller so if you just remove your try/catch blocks, that's what will happen.
And, the return new Promise() you have is entirely superfluous and unnecessary (referred to as an anti-pattern). You can just remove it and return data instead.
Note: Since the code you show does not show any dependency between your first and second fetch() calls, you could run them in parallel and perhaps finish them faster.
async function fetchWeatherAPI(){
//data that I need
let data = {};
const [result1, result2] = await Promise.all([
fetch(url1).then(r => r.json()),
fetch(url2).then(r => r.json())
]);
return {...data, result1.usefulData, result2.usefulData};
}
Or, I often use a helper function in my code when doing a lot of fetch() calls:
function fetchJson(...args) {
return fetch(...args).then(r => r.json());
}
async function fetchWeatherAPI() {
//data that I need
let data = {};
const [result1, result2] = await Promise.all([
fetchJson(url1),
fetchJson(url2)
]);
return { ...data, result1.usefulData, result2.usefulData };
}

Async/Await on Callback not waiting

I am querying AMPS SOW using javascript API. My functions look like this:
sow_query = async(topic, filter, options) => {
await this.connection
await this.client.sow(this.onMessage, topic, filter, options)
return this.message
}
onMessage = (message)=>{
this.message.push(message.data)
}
//Calling functions
const getData = async(date) =>{
let data = await getAmpsData(date)
}
async getAmpsData(date){
const filter = "some filter"
const data = await ampsClient.sow_query(topic, filter)
data.forEach((element) => console.log(element))
}
It looks like my call to ampsClient.sow_query doesnt wait for the callback to finish so returns an empty list. Therefore, I cannot loop through the data in my calling function getAmpsData.
I know it has to do with async/await because if I simply do console.log(data) in getAmpsData i can see the data (perhaps after 2 seconds) when the promise is resolved. Is there anyway i can get this working?
If I understand you correctly, data in getAmpsData is an array as expected, but data in getData is undefined. It's not an async/await problem, you just forgot to add return data; to getAmpsData.
I not sure about what package you are using. But, maybe, it using a callback function to get the result of .sow function - message.data.
With your logic, onMessage function will be called after data.forEach done, you can try adding a console.log line to onMessage function.
Maybe, the package has an important reason to do that. But, to fix this issue, you can wrap .sow function into a promise.
sow_query = async (topic, filter, options) => {
await this.connection // ???
return new Promise((resolve) => { // wrap in a promise
this.client.sow( // no need to wait .sow
(message) => {
resolve(message.data); // just resolve when we get the message's data
},
topic, filter, options)
});
}
//Calling functions
const getData = async (date) => {
let data = await getAmpsData(date)
}
async getAmpsData(date) {
const filter = "some filter"
const data = await ampsClient.sow_query(topic, filter)
data.forEach((element) => console.log(element))
}

Why doesn't the catch in async await code fire?

is there any .catch() method like there is with Promises for async await style of code?
Here's an example of a code written via Promise:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
function getData(url){
fetch(url)
.then(response => response.json())
.then(json => console.log(json))
.catch( err => console.log('cannot load api'))
}
getData(apiURL);
getData(badURL);
A simple function to try to load data and if not, display a basic error message. Now I was trying to transcribe this into async/await style code, issue was, I could not really figure out a way to write this with catch()
My best guess was to try try - catch but the catch part doesn't work:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
try {
const json = await response.json();
console.log(json);
} catch (e) {
console.log('cannot load api');
}
}
getData(apiURL);
getData(badURL);
This loads the object API just fine, but never seems to go into the catch{} block despite being passed incorrect url.
Any idea what am I doing wrong?
As pointed out in the comments by #l-portet, this is because the code inside try { } block does not actually fail!
.json() will return a promise, regardless of the content of the parsed body text, so even though the initial fetch() fails, you can still call .json() on it - albeit it's completely redundant as it won't return anything meaningful.
Putting the fetch() request inside the try { } block does result in the expected behaviour:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
try {
const response = await fetch(url);
const json = await response.json();
console.log(json);
} catch (e) {
console.log('cannot load api');
}
}
getData(apiURL);
getData(badURL);
One thing you should be aware is that when an async function is executed, it always returns a promise, regardless the exit condition of the function.
If the function has an explicit return (or completes without crashing) the promise will be resolved to the value it returned (or to undefined if there was no explicit return), if the function throws, the promise will be rejected, passing the thrown error object.
Knowing that you could simply handle the error where you use the function, for example:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
return await response.json();
}
getData(apiURL).then(data => console.log(data));
getData(badURL).catch(err => console.log('error:', err));
IMHO handling the error closely where you have a use-case of the function makes more sense, since normally when you expect to have an error is because we have a way to handle it (maybe try another API url in this example).
One pattern that I've been using lately is to wrap promises in a way they resolve returning a tuple, in the convention of [error, value] (similar to the way the Go programming language handle async error), in that way for instance you could handle the error in the specific getData call, for example:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
return await response.json();
}
// simple utility function
const safePromise = promise =>
promise.then(data => [null, data]).catch(err => [err, undefined]);
(async () => {
const [err, json] = await safePromise(getData(apiURL))
if (err) {
// handle the error
}
console.log(json)
const [error, data] = await safePromise(getData(badURL))
if (error) {
console.log('Error:', error);
}
})()
Check the following library which basically ships this pattern:
await-to-js

Using async in a promise because of db-calls. How can I fix this antipattern?

I have a Firebase Function which sends back data from databases. The problem is sometimes I have to return data all of 3 collections, sometimes only need from 1 and sometimes 2 of them. But this is an antipattern. How can I improve my code?
Right now I'm creating a function, which returns a promise, in which I'm using await for getting db values and this is wrapped in try{} block.
module.exports.getList = (uid, listType) => new Promise(async (resolve, reject) => {
let returnValue = [];
try {
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
} catch (e) {
return reject(e);
}
return resolve(returnValue);});
How should I modify this snippet in order to not be an antipattern? Or is it not because of the try-catch block?
You can make the getList function async instead, without new Promise or try/catch:
module.exports.getList = async (uid, listType) => {
const returnValue = [];
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
return returnValue;
};
Calling it will return a Promise that rejects with an error if there's an asynchronous error, or it will resolve to the desired array.
Note that unless there's a good reason to await each call in serial, you can use Promise.all instead, so that the requests go out in parallel, and make the code a lot more concise in the process:
module.exports.getList = (uid, listType) => Promise.all(
['alist', 'blist', 'clist']
.filter(name => listType.contains(name[0]))
.map(name => db.collection(name).doc(uid).get())
);

When using async await, how do you specify a callback?

I was looking at how to use transactions in:
https://node-postgres.com/features/transactions
But in the following code example:
const { Pool } = require('pg')
const pool = new Pool()
(async () => {
// note: we don't try/catch this because if connecting throws an exception
// we don't need to dispose of the client (it will be undefined)
const client = await pool.connect()
try {
await client.query('BEGIN')
const { rows } = await client.query('INSERT INTO users(name) VALUES($1) RETURNING id', ['brianc'])
const insertPhotoText = 'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'
const insertPhotoValues = [res.rows[0].id, 's3.bucket.foo']
await client.query(insertPhotoText, insertPhotoValues)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
throw e
} finally {
client.release()
}
})().catch(e => console.error(e.stack))
It seems that the function will execute immediately. Also there doesn't seem to be a way to specify a callback. Would it make sense to place the entire block from "(async()...." into a function, and then in the final statement before the end of the try block, add :
await callbackfunction();
Does that make sense? What would be a better way to add a callback function ?
The point of await is that you don't use a callback. It returns the result of resolving the promise.
Without await:
do_something_asyc.then(function (data) { alert(data); });
With await:
var data = await do_something_asyc();
alert(data);

Categories

Resources