So I've made a bit of reusable code here for node, and I'm applying it via async / await. Albeit I'm sure I am misunderstanding a lot here when working with this... But, I swear, I have one project I'm using this code that it works, and another where it doesn't.
Im using request and request-promise.
UrlRequest: function( opts ) {
return new Promise( (resolve, reject) => {
request( opts,
function(error, request, body) {
if (error)
reject( {error: true, msg: error} );
else
resolve( {body, request} );
});
})
.catch(err => reject( {error: true, msg: err} ));
}
I am fairly sure the .catch() is wrong. But it didn't error out in my 1st project. So i'm trying to figure out the proper way of doing this. The few articles I've looked through is where I came up with this function for usage. I also know if any error actually happens ( this case included ), it will throw a UnhandledPromiseRejectionWarning error. So how is this properly handled?
How I use it:
(async () => {
var result = await Promise.UrlRequest( {
url: "...",
method: "GET",
headers: DefaultHeaders
} );
// do stuff with result...
}) ();
Since you already installed request-promise, you don't need constructing the Promise as you are doing. Simply use the it instead of request then you would have a promise returned. Something similar to this should work:
const request = require('request-promise')
request(opts)
.then((res) => {
// Process res...
})
.catch((err) => {
// Handle error...
});
You can proceed to wrap it in your UrlRequest function and use with async as follows:
UrlRequest: async ( opts ) => {
try {
const response = await request(opts);
return response;
} catch (error) {
// Handle error
}
}
In the case that you want to use then() and catch(), you can do this:
UrlRequest: ( opts ) => {
return request(opts)
.then(response => response)
.catch (error) {
// Handle error
}
}
With request-promise, you don't need to write your own Promise wrapper
// make sure you're using the promise version
const request = require('request-promise')
var opts = {
...
resolveWithFullResponse: true // <--- <--- to get full response, response.body contains the body
};
// if you dont plan to use UrlRequest as constructor, better name is starting with lowercase: urlRequest, some naming convention
UrlRequest: async function( opts ) {
let res;
try {
res = await request(opts);
} catch (e) {
// handle error
throw e
}
return res;
}
Note: async function wraps the return in Promise
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);
}
})
});
I have this piece of code that calls a function getTableData and expects a Promise in return.
function populateTableRows(url) {
successCallback = () => { ... };
errorCallback = () => { ... };
getTableData(url, successCallback, errorCallback).then(tableData => {
// do stuff with tableData
}
}
This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)
In getTableData, I'm currently using $.ajax like so
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
const ajaxOptions = {
type: 'POST',
url: url,
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: { // some data }
};
return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}
This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).
When converting the request over to use fetch, I have something like this
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: { // some data }
})
.then(response => {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
successCallback(json);
return json;
} else {
return json.then(error => {throw error;});
}
}).catch((error) => {
errorCallback(error);
return
});
Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.
Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.
When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.
For example:
apiCall()
.catch((error) => {
console.log(error);
return true; // error handled, returning true here means the promise chain can continue
})
.then(() => {
console.log('still executing if the API call fails');
});
What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.
apiCall()
.catch((error) => {
console.log(error); // "handled", but we're still not done
throw error; // instead of returning true, we throw the error further
// 👆 this can also be written as `return Promise.reject(error);`
})
.then(() => {
console.log('not executing anymore if the API call fails');
})
.catch((error) => {
// handle the same error we have thrown from the previous catch block
return true; // not throwing anymore, so error is handled
})
.then(() => {
console.log('always executing, since we returned true in the last catch block');
});
By the way, what you return from one then/catch block, the following one will get it as a param.
apiCall()
.then((response) => {
/* do something with response */;
return 1;
})
.catch((error) => { return 'a'; })
.then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise
In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.
.catch((error) => {
errorCallback(error);
return Promise.reject();
});
should be enough to keep the returned Promise rejecting.
Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:
var reqPromise = fetch(url, {
// ...
})
.then(response => {
// ...
return json.then(error => {throw error;});
});
reqPromise.catch((error) => {
errorCallback(error);
return
});
return reqPromise;
I am trying to create a function with a GET request that returns a portion of the data from the GET request. However, it keeps returning before the data is retrieved, so I keep getting "undefined". How can I set this up so it actually waits for the data to be set before returning?
let getInfo = async () => {
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => { // Promise being here DOES work
request.on('response', (response) => {
response.on('data', (chunk) => {
//return new Promise((resolve, reject) => { //Promise being here does NOT work
let body = JSON.parse(chunk)
let info = body.data
if (info){
resolve(info);
}
reject();
//})
});
});
request.write('')
request.end()
}).then(data => {
console.log("From then: "+data)
return data
})
}
getInfo().then(data => {
console.log("From outside: "+data)
})
Edit: This is the updated version that still does not work. I am trying to use the native electron method and I don't see why this doesn't work. The "From then:" part displays the info correctly. But when run "From outside:" it prints undefined. Does the issue have anything to do with the response.on being nested inside the request.on?
Solution: As #NidhinDavid showed in his answer, the issue was that the promise was inside the 'response' listener. Moving the 'GET' request from start to finish inside the Promise fixed it to giving the correct output. I have updated my code to reflect that for future individuals.
let getInfo = () => {
let info;
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => {
request.on('response', (response) => {
response.on('data', (chunk) => {
request.write('')
request.end()
let body = JSON.parse(chunk)
info = body.data
if (info) {
resolve(info)
} else {
reject('Something went wrong');
}
});
});
})
}
getInfo()
.then(data => {
// this will be your info object
console.log(data)
})
.catch(err => {
// this will log 'Something went wrong' in case of any error
console.log(err)
})
You need to return inside your, on type event handler. Read more about asynchronous code and synchronous code here
I couldn't find the net module and the one which is included with Nodejs do not have request method. So to get the similar concept of event emiters and promise I am using http module and doing a http request to fetch json and parse it
'use strict'
var https = require('https');
const getInfo = async () => {
// create a new promise chain
// remember it is a chain, if one return is omitted
// then the chain is broken
return new Promise((resolve, reject) => {
var options = {
host: 'support.oneskyapp.com',
path: '/hc/en-us/article_attachments/202761727/example_2.json'
};
// start the request
https.request(options, function (response) {
var str = '';
// data arrives in chunks
// chunks needs to be stitched together before parsing
response.on('data', function (chunk) {
str += chunk;
});
// response body obtained
// resolve (aka return) the result
// or parse it, or do whatever you want with it
response.on('end', function () {
resolve(str)
});
// errors are another event
// listen for errors and reject when they are encountered
response.on('error', function (err) {
reject(err)
})
}).end()
})
}
//*********************************************
// using async await
//*********************************************
// if this is the entry point into app
// then top-level async approach required
(async ()=>{
try{
let data = await getInfo()
console.log("From ASYNC AWAIT ")
console.log(JSON.stringify(JSON.parse(data)))
}
catch (err) {
console.log("operation failed, error: ", err)
}
})();
//************************************************
// using promise chains
//************************************************
getInfo()
.then((data)=>{
console.log("FROM PROMISE CHAIN ")
console.log(JSON.stringify(JSON.parse(data)))
})
.catch((err)=>{
console.log("operation failed, error: ", err)
})
Tyr this, it might works for you,
let info;
const getInfo = async (_url)=>{
const response = await fetch(_url);
const data = await response.json();
info = data;
} ;
const url = "some url";
getInfo(url);
console.log(info);
Async function always returns a promise, so either consume that promise or internally await the data and assign it to some variable.
Check for the valid data required in info by logging it to the console.
I'm having some issues understanding how the Promise functionality works, I have previously used Bluebird but I wanted to try to learn the new await/async standard in order to improve as a programmer. I have used async/await and created promises where I feel appropriate however the functions are still executing out of order.
I'm running this on the latest version of Node with Webpack, I'm not getting any meaningful errors. It runs fine just not as expected. My output when running it is:
Searching the Web for: Test String
Web search Completed!
Promise { <pending> }
Response Handler Completed!
Ideally I'd like it to respond with:
Searching the Web for: Test String
Response Handler Completed
Web search Completed
And then return the output of my response handler.
Can anyone spot my mistake?
const https = require('https');
// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';
const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';
const response_handler = async (response) => {
return new Promise((resolve, reject) => {
let body = '';
response.on('data', (d) => {
body += d;
resolve(body);
});
response.on('end', () => {
console.log('\nRelevant Headers:\n');
for (const header in response.headers)
// header keys are lower-cased by Node.js
{
if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
}
body = JSON.stringify(JSON.parse(body), null, ' ');
//console.log('\nJSON Test Response:\n');
//console.log(body);
});
response.on('error', (e) => {
console.log(`Error: ${e.message}`);
});
console.log('Response Handler Completed!');
});
};
const bing_web_search = async (search) => {
return new Promise((resolve, reject) => {
console.log(`Searching the Web for: ${search}`);
const request_params = {
method: 'GET',
hostname: host,
path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
},
};
const req = https.request(request_params, response_handler);
console.log('Web search Completed!');
console.log(req.body);
req.end();
});
};
module.exports = {
search: async (search) => {
if (subscriptionKey.length === 32) {
const result = await bing_web_search(search);
console.log('Search Completed');
} else {
console.log('Invalid Bing Search API subscription key!');
console.log('Please paste yours into the source code.');
}
},
};
A bit late but the following should set you on the way, I made changes to the code. If you have any questions please let me know.
const https = require('https');
// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';
const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';
const response_handler = (resolve,reject) => (response) => { // no need for async, you return a promise
//this one does not return anything, it's the handler for the response and will resolve
// or reject accordingly
let body = '';
response.on('data', (d) => {
body += d;
//cannot resolve yet, we're not done
// you can resolve on end maybe? I don't know nodejs http
// if end event is called when request fails then end would not
// be the correct way either, better use fetch api
//resolve(body);
});
response.on('end', () => {
console.log('\nRelevant Headers:\n');
for (const header in response.headers)
// header keys are lower-cased by Node.js
{
if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
}
body = JSON.stringify(JSON.parse(body), null, ' ');
resolve(body);//resolving the promise returned by bing_web_search
//console.log('\nJSON Test Response:\n');
//console.log(body);
});
response.on('error', (e) => {
console.log(`Error: ${e.message}`);
//need to reject with the error
reject(e);
});
console.log('Response Handler Completed!');
};
//no need to specify async, you are not awaiting anything
// you are creating a promise, when using non promise asynchronous
// functions that work with callbacks or event emitting objects
// you need resolve and reject functions so you have to return
// new Promise(
// (resolve,reject)=>somecallbackNeedingFunction((err,result)=>
// err ? reject(err) : resolve(result)
// )
// )
const bing_web_search = (search) => {
return new Promise((resolve, reject) => {
console.log(`Searching the Web for: ${search}`);
const request_params = {
method: 'GET',
hostname: host,
path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
},
};
const req = https.request(
request_params,
response_handler(resolve,reject)//passing this resolve and reject
);
//no, request not completed, we just started
console.log('Web search Completed!');
// console.log(req.body); // nothing to log here
req.end();
});
};
module.exports = {
search: async (search) => {
if (subscriptionKey.length === 32) {
//did not change anything bing_web_search returns a promise
// so you can just await it
const result = await bing_web_search(search);
console.log('Search Completed');
//this will resolve with the results
return result
} else {
console.log('Invalid Bing Search API subscription key!');
console.log('Please paste yours into the source code.');
//the caller of this function can handle the rejection
return Promise.reject('Invalid Bing Search API subscription key!');
}
},
};
[update]
Your comment suggest that you do not call search correctly or handle the promise it returns correctly. You have no control over how long a response takes so in a set of responses the first request may return last. This is why you have Promise.all
const searchObjects = [s1,s2];
const Fail = function(reason){this.reason=reason;};
Promise.all(
searchObjects.map(
searchObject => obj.search(searchObject)
.then(
x=>[x,searchObject]//if resolve just pass result
,err =>new Fail([err,searchObject])//if reject add a Fail object with some detail
)
)
)
.then(
results => {
console.log(
"resolved results:",
results.filter(([r,_])=>(r&&r.constructor)!==Fail)
);
console.log(
"failed results:",
results.filter(([r,_])=>(r&&r.constructor)===Fail)
);
}
)
If you have a lot of searches then maybe you want to throttle the amount of responses withing a certain time period or active connections. Let me know if you need help with that.
I have a function that calls a promise. On success or failure I would like to return some data beyond what the promise returns.
I thought this might work:
function foo() {
const extra = 'bar'
return thepromise().then((res) => {
return {
result: res,
data: extra
}
}, (err) => {
// this will not happen if an error is thrown in the called promise
return {
result: res,
data: extra
}
})
}
foo().then((res) => { }, (err) => { // error result ends up here })
However this does not work. if an error is thrown in the thepromise it will not call the catch block but instead the catch block of foo().
What is the proper way to handle this? I am successful with a try/catch block but I am not sure this is the best approach:
function foo() {
const extra = 'bar'
return new Promise((resolve, reject) => {
try {
return thepromise(p)
} catch (e) {
reject(e)
}
})
.then(function(res) {
return {
result: res,
data: extra,
status: 'success'
}
}, function(err) {
return {
result: err,
data: extra
status: 'error'
}
})
}
Any function designed to return a promise should not be throwing any sort of exception or error.
If it does, it should be considered buggy.
To fix a buggy method, instead of throwing an exception, reject the returned promise.
Sometimes you're not in a position to fix the underlying bug, such as when you're using someone else's API. If this is the case, the first and most important thing to do is report the bug to the original authors so that the underlying issue can be fixed.
After reporting the issue, you can wrap the buggy method in a simple utility to fix the issue:
function fixBrokenPromise(promise/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1),
ret;
try {
ret = promise.apply(null, args);
} catch (ex) {
ret = Promise.reject(ex);
}
return ret;
}
This can be called as:
fixBrokenPromise(thepromise/*, args you want to pass to thepromise */)
.then(...resolve...,
...reject...);
If you're willing to entertain extensions to Promises, bluebird has a try method that does just what you need:
import Promise from 'bluebird';
// ...
return Promise
.try(thepromise)
.then(res => ({
result: res,
data: extra
})
.catch(err => ({
result: res,
data: extra
});
The error is considered handled at .catch(), which would return a resolved promise to chained .then(). You can throw the error to the next .catch()
function foo() {
const extra = 'bar'
return thepromise().then((res) => {
return {
result: res,
data: extra
}
}, (err) => {
throw new Error(JSON.stringify({
result: err || "no rejection reason provided",
data: extra
}))
})
}
var thepromise = () => Promise.reject();
foo().then(data => console.log("fulfilled", data))
.catch(err => console.log("catch", JSON.parse(err.message)))