I have a request made with "request" from NodeJs, and I want to get the data with a promise. All good because the console shows me the information that I am requested. The problem is when I call the promise function, and the console shows me this. How can I get the real value?
let leagueTier = (accId) => new Promise(async (resolve, reject) => {
request({
url: `https://${reg}.api.riotgames.com/lol/league/v4/entries/by-summoner/${accId}${key}`,
json: true
}, (error, response, body) => {
if (!error && response.statusCode === 200) {
resolve(body[0].tier.toLowerCase())
} else {
reject(Error('It broke'))
}
})
}).then(function(res) {
return res;
})
This is where I call the function
.attachFiles([`./images/${leagueTier(body.id)}.png`])
(node:5868) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, stat 'D:\Dev\shen-bot\images[object Promise].png'
How can I access the string of the request I made? Maybe I shouldn't use promises?
The leagueTier return value will be a promise that eventually resolves or rejects into a value. You can't simply add the return value as part of a string. You can either use await or then to wait for the value to come through.
// asuming you are in async function context
.attachFiles([`./images/${await leagueTier(body.id)}.png`]);
// or
leagueTier(body.id).then(tier => {
.attachFiles([`./images/${tier}.png`]);
});
Note that your current code:
.then(function(res) {
return res;
})
Does absolutely nothing and can be omitted.
Related
I have an HTTP API that returns JSON data both on success and on failure.
An example failure would look like this:
~ ◆ http get http://localhost:5000/api/isbn/2266202022
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0
{
"message": "There was an issue with at least some of the supplied values.",
"payload": {
"isbn": "Could not find match for ISBN."
},
"type": "validation"
}
What I want to achieve in my JavaScript code is something like this:
fetch(url)
.then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp.json();
} else {
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
}
})
.catch((error) => {
// Do something with the error object
}
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
Well, the resp.json promise will be fulfilled, only Promise.reject doesn't wait for it and immediately rejects with a promise.
I'll assume that you rather want to do the following:
fetch(url).then((resp) => {
let json = resp.json(); // there's always a body
if (resp.status >= 200 && resp.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
})
(or, written explicitly)
return json.then(err => {throw err;});
Here's a somewhat cleaner approach that relies on response.ok and makes use of the underlying JSON data instead of the Promise returned by .json().
function myFetchWrapper(url) {
return fetch(url).then(response => {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
});
}
// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));
// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));
The solution above from Jeff Posnick is my favourite way of doing it, but the nesting is pretty ugly.
With the newer async/await syntax we can do it in a more synchronous looking way, without the ugly nesting that can quickly become confusing.
async function myFetchWrapper(url) {
const response = await fetch(url);
const json = await response.json();
return response.ok ? json : Promise.reject(json);
}
This works because, an async function always returns a promise and once we have the JSON we can then decide how to return it based on the response status (using response.ok).
You would error handle the same way as you would in Jeff's answer, however you could also use try/catch, an error handling higher order function, or with some modification to prevent the promise rejecting you can use my favourite technique that ensures error handling is enforced as part of the developer experience.
const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'
// Example with Promises
myFetchWrapper(url)
.then((res) => ...)
.catch((err) => ...);
// Example with try/catch (presuming wrapped in an async function)
try {
const data = await myFetchWrapper(url);
...
} catch (err) {
throw new Error(err.message);
}
Also worth reading MDN - Checking that the fetch was successful for why we have to do this, essentially a fetch request only rejects with network errors, getting a 404 is not a network error.
I found my solution at MDN:
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
Promise.any([coffee, tea]).then(value => {
let objectURL = URL.createObjectURL(value);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log(e.message);
});
Maybe this option can be valid
new Promise((resolve, reject) => {
fetch(url)
.then(async (response) => {
const data = await response.json();
return { statusCode: response.status, body: data };
})
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.body);
} else {
reject(response.body);
}
})
});
Hello I'm a complete beginner in node js and I just learned about promises and I'm trying to using them in my code
but when I try to console.log this function I keep getting undefined and I don't know why
I will be very thankful if you help me
function weather(location) {
const request = require('request');
const errorMessage = 'Something went wrong. Please check you internet connection or the URL you provided.';
new Promise((resolve, reject) => {
const geoUrl = `https://api.mapbox.com/geocoding/v5/mapbox.places/
${location}.json?access_token=pk.eyJ1Ijoic3F1YXJlc2NyaXB0IiwiYSI6ImNrc3lwZmdtejFpdzQycHB1eTVpODNwdmYifQ.izw6cVMKDZme4KJwxHdxjw`;
request({
url: geoUrl,
json: true
}, (error, request) => {
if (error) {
reject(errorMessage)
} else {
resolve(`http://api.weatherstack.com/current?access_key=
c704f108fc10fbbdb15c384daff85a20&query=${request.body.features[0].center.reverse()}`)
}
})
}).then(url => {
request({
url: url,
json: true
}, (error, request) => {
if (error) {
return errorMessage
} else {
const currData = request.body.current;
return `${currData.weather_descriptions[0]}, It's currently ${currData.temperature} degrees out. There is a ${currData.humidity}% chance of rain.`;
}
});
}).catch(message => message)
};
console.log(weather('NYC'))
You need to return the promise..
return new Promise((resolve,reject)=>{
And then the console will log "Unresolved promise." Or something like that. If you want to wait for the promise to resolve, you need to use await or the then method:
weather('NYC').then(console.log)
You also need to make sure your promise is being resolved or rejected:
if (error){reject(errorMessage)}
else {
const currData=request.body.current;
resolve(`${currData.weather_descriptions[0]}, It's currently ${currData.temperature} degrees out. There is a ${currData.humidity}% chance of rain.`);
}
All these little errors would be easier to spot if your code was formatted properly.
Also, don't put your private access tokens in public forums.
You can find lots of tutorials on how promises work around the internet. Here's the MDN manual page.
I can not get the return object out of my function, so I can reuse it in the next function which should be called by my promise.then().
Struggling with researching, and can not find the solution.
engine.log is basically equivalent to console.log in my application.
firstRequest
.then( function (result) {
return secondRequest(result)
},
error => engine.log(error)
)
.then(
result => engine.log(result), // result is undefined, need the object here
)
let firstRequest = new Promise(function(resolve, reject) {
http.simpleRequest({
'method': 'GET',
'url': theUrl,
'dataType': 'json',
'timeout': 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error)
reject()
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status)
reject()
}
myObject = JSON.parse(response.data)
resolve(myObject)
})
});
function secondRequest(request) {
http.simpleRequest({
'method': 'GET',
'url': theSecondUrl,
'dataType': 'json',
'timeout': 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error);
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status);
}
myObject = JSON.parse(response.data)
return myObject // this is not being returned, to the .then()
})
}
Firstly, I see redundant code, so I would consolidate your requests into a single more generic function. It seems your desire not to do this is you feel you have to pass a 'request' parameter to your second function, but notice that you never use it. Just pass a url in both cases.
In your generic function, which below i'm calling 'makeRequest', be sure to return the promise, or else you'll get undefined. Also, be sure to call resolve on the parsed object, not return;
Also, I'd use catch for your errors, that way you'll catch it whenever an error occurs, whether it's in the first then or the second one.
This is untested, by the way, but I think the general points are in there.
makeRequest('somewhere')
.then(result => makeRequest('theSecondUrl'))
.then(result => engine.log(result))
.catch(error => engine.log(error));
function makeRequest (url, method, datatype, timeout) {
return new Promise(function(resolve, reject) {
http.simpleRequest({
'method': method || 'GET',
'url': url,
'dataType': datatype || 'json',
'timeout': timeout || 6000,
}, function (error, response) {
if (error) {
engine.log("Error: " + error)
reject()
}
if (response.statusCode != 200) {
engine.log("HTTP Error: " + response.status)
reject()
}
// use 'let' or else you've made a globally scoped variable
let parsed = JSON.parse(response.data)
resolve(parsed);
});
})
}
Javascript promises are asynchronous. Meaning they are resolved in the event loop, if the promise is resolved as soon as your call to .then() callback, then you will get the value in the next function as you expect.
Javascript promises are made asynchronous since typical API calls or database retrieving may involve network delays or any other delays to return the data. Thus the users will see these delays as performance issues. To avoid this kind of effect Javascript event loop is doing its job as running promises asynchronously. Meaning the code after promises is run most probably before it gets resolved or rejected. Therefore your next function will not have the value of first promise when its code is run.
In the next iteration or 1+ iterations of the event loop, your promise may get resolved/rejected and then may have populated with the corresponding value.
The solution =>
You should use async await to make any asynchronous code synchronous in javascript.
You could read more about it here.
https://javascript.info/async-await
I have written a function in 'apiCalls.js' file and have the following function:
function getAllData() {
new Promise(function(resolve, reject) {
async.parallel([
function(callback) {
request('http://urlcall', function(error, response, body) {
if(!error && response.statusCode == 200) {
return callback(null, body);
}
return callback(error || new Error('Response non-200'));
});
},
function(callback) {
request('http://urlcall', function(error, response, body) {
if(!error && response.statusCode == 200) {
return callback(null, body);
}
return callback(error || new Error('Response non-200'));
});
},
],
function(err, results) {
if(err) {
console.log(err);
reject(err);
}
console.log(results);
resolve(results);
});
});
}
I am then calling this function in my app.js file:
apiCalls.getAllData().then(function(returned) {
console.log(returned);
res.render('home');
});
I am getting an error where the value returned in then is undefined:
TypeError: Cannot read property 'then' of undefined
I am not sure where I am going wrong. I have resolved the promise and then used that value in the then function. Am I missing something here? I am new to using promises and asynchronous programming so am I missing some understanding here of how it is supposed to work?
First off, you really don't want to mix promises with plain callbacks with the async library. You need to pick one model to work with (promises or callbacks) and program all your control flow in one model. So, if you're moving to promises (which most of the world is), then skip the async library entirely. In this particular case, Promise.all() serves your purpose for two parallel requests.
Then, look to promisify the lowest level asynchronous operations you have as that lets you use promises for all your control flow and error handling. In your case, that's the request library which already has a promisified version called request-promise. In addition, it already checks for a non-2xx status for you automatically (and rejects the promise) so you don't have to code that. So, it appears you could replace every you have with just this:
const rp = require('request-promise'); // promise version of the request library
function getAllData() {
return Promise.all([rp('http://urlcall1'), rp('http://urlcall2')]);
}
Then, you can use it with .then():
apiCalls.getAllData().then(returned => {
console.log(returned);
res.render('home', returned);
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
Can someone help me understand why this returns a pending promise, rather than the data?
async function toJson (filepath) {
const file = fs.createReadStream(filepath)
let json = new Promise((resolve, reject) => {
Papa.parse(file, {
header: true,
complete (results, file) {
resolve(results)
},
error (err, file) {
reject(err)
}
})
})
let result = await json
return result.data
}
If I change the return result.data line to console.log(result.data), it logs the data array to the console, as expected. Why won't it simply return that array?!?!
As Roamer-1888 added in the comments, async functions always return a Promise, even if you await inside it and then return the data it will return as a Promise.
In the function's caller, you will have to await the Promise or use .then() on it in order to access the delivered data.
The toJson function could be better written to just return a Promise like this
function toJson (filepath) {
const file = fs.createReadStream(filepath)
return new Promise((resolve, reject) => {
Papa.parse(file, {
header: true,
complete (results, file) {
resolve(results.data)
},
error (err, file) {
reject(err)
}
})
})
}
Now when you call toJson(), you can use either await if you are inside of an async function or chain .then() on the returned Promise to access the data.
async function main() {
try {
const data = await toJson(filepath)
// do something with the data...
} catch (err) {
console.error('Could not parse json', err)
}
}
Or with .then()
toJson('path')
.then(console.log)
.catch(console.log)
You will be able to catch errors from the underlaying FileReader (thanks to calling reject inside the error function). Keep in mind that by calling resolve with results.data you put aside results.errors and results.meta which contain useful information about the read csv.