Return promise from a http request - javascript

Making a precheck to see if images in an api actually exists. I'm using 'q' as my promise library and 'request' lib to make the http request. I'm trying to return an array of all the successfully requested images as so:
.then((data)=> {
return q.all(data.map((elem) => {
let deferred = q.defer()
request(`https://address/path/${elem}.jpg`, (error, response) => {
if (!error, response.statusCode === 200) {
deferred.resolve(elem)
}
})
return deferred.promise
})
)
})
.then((result)=> {
console.log('Array of existing images', result)//Result should contain all image id's which had status code 200
})
I would expect this to return all results which had a request status of 200. With q.all() I expect the function to return once all promises are fulfilled.

I think, you shuld add one chain, where you clean non 200 images
for exemple, lik that
.then((data)=> {
return q.all(data.map((elem) => {
let deferred = q.defer()
request(`https://address/path/${elem}.jpg`, (error, response) => {
if (!error) {
deferred.resolve({img:elem, status : response.statusCode})
}else{
deferred.reject({elem: 'error'})
}
});
return deferred.promise
})
)
})
.then((result)=> {
let onlyGoodImages = [];
result.map((elem) => {
if (elem.status == 200){
onlyGoodImages.push(elem.img);
}
});
return onlyGoodImages;
}
)
.then((result)=> {
console.log('Array of existing images', result)//Result should contain all image id's which had status code 200
})

Related

After post request, continue checking for completion | Node.js

I'm making a post request with a good amount of data that will take about a minute to finish saving. The hosting service I'm using for this will time out requests after 5 seconds, so I need to set this up to periodically check if the data saving is complete to give a final update.
I'm using request-promise, and have looked at both setTimeout and setInterval approaches. In my latest attempt (below) I'm using a setTimeout approach, but my second then keeps being called pretty much immediately. I want this to hang out in the first then stage until it's checked a bunch of times (24 here) or actually finished.
I might have a totally wrong approach here, but I'm not finding examples of the thing I'm trying to reference. Any direction to a good example of this or where I'm going wrong would be greatly appreciated.
const request = require('request-promise');
function checkFiles () {
return request({
uri: `${process.env.ROOT_URL}/api/v1/get/file-processing`,
method: 'GET',
json: true
})
.then(res => { return res; })
.catch(err => { return err; });
}
async function init () {
const filesPostOptions = {/* request options */};
await request(filesPostOptions)
.then(async status => { // THEN #1
if (status.status === 201) {
return status;
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
return status;
} else {
setTimeout(checkIt, 5000);
}
})
.catch(err => {
console.error(err);
});
};
checkIt();
})
.then(status => { // THEN #2
if (!status.status) {
throw Error('Post request timed out.');
}
return status;
})
.catch(err => {
err = err.error ? err.error : err;
console.error(err);
});
}
The post response will deliver a response with a status property (the status code) and a message property.
You need to control the return in "THEN #" by adding a Promise:
.then(async status => { // THEN #1
return new Promise((resolve, reject) => { // <---- prevent an immediate return
if (status.status === 201) {
return resolve(status);
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
resolve(status);
} else {
setTimeout(checkIt, 1000);
}
})
.catch(err => reject(err));
};
checkIt();
})
})

Figuring the complexity of Promise.all

I have been struggling for quite some time to get this multiple async nodejs request apis to work but unfortunately i am not able to get them work.
Index.js Code:
service.get(
"/restraunts",
versionRoutes({
"1.0.0": getRestrauntsList
})
);
function getRestrauntsList(req, res, next) {
console.log("Started getRestrauntsList");
file1
.appEnvironment(req, res, next)
.then(function(result) {
return file2.getRestrauntsList(req, res, next);
})
.then(function(result) {
res.status(200).send(result);
return;
})
.catch(function(errorResult) {
res.status(500).send(errorResult);
return;
});
}
File2.js
module.exports = {
getRestrauntsList: function(req, res, next) {
console.log("getRestrauntsList started..");
var cities = [1, 2, 3, 4, 5];
let restrauntsList = [];
let urlArray = [];
var restrauntsListPromise = cities.map(function(id) {
return new Promise(function(resolve, reject) {
var options = {
method: "GET",
url: "someurl/" + id + "/restaurants",
headers: {
"AUTH-TOKEN": "TOKEN"
}
};
request(options, function(error, response, body) {
if (error) {
if ("message" in error) {
errorMsg = error.message;
var result = {
status: "error",
message: errorMsg
};
} else {
var result = {
status: "error",
message: "Resource Timeout."
};
}
reject(result);
return promise;
}
console.log(
"Response: " + JSON.stringify(response)
);
if (response.statusCode === 200 || response.statusCode === 201) {
body = JSON.parse(body);
if (body.success) {
let result = {
status: "success",
data: body.result
};
resolve(result);
} else {
let result = {
status: "error",
message: body.error
};
reject(result);
}
} else {
let result = {
status: "error",
message: body.error
};
reject(result);
}
});
});
});
console.log('restrauntsListPromise:' + JSON.stringify(restrauntsListPromise));
Promise.all(restrauntsListPromise).then(function(result) {
var content = result.map(function(restraunts) {
return restrauntsList.push(restraunts.body);
});
// res.send(content);
resolve({
restrauntsList: restrauntsList
});
return promise;
});
},
};
Ideally i expect to get the response of all the apis in the
restrauntsListPromise
and then using Promise.all i should iterate all the promises and formulate my required object.
The response of my code however is
restrauntsListPromise:[{},{},{},{},{}]
and then
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Ideally what should happen is i should be able to pass the combined result of all the five apis calls as a single object back to the calling promise here
.then(function(result) {
res.status(200).send(result);
return;
})
The problem being the method getRestrauntsList finishes execution and then after some time, i get the responses of the apis.
The problem being the method getRestrauntsList finishes execution and then after some time, i get the responses of the apis.
This is because you're not returning a promise from the getRestrauntsList().
There are few items that needs to addressed to make it work
1. Remove the unused variables
return promise; // both inside promise.all[] and request()
There is no declared variable named promise. So, you can remove it.
2. Accessing .body instead of .data
You're resolving as resolve({status: "success", data: body.result}); But When you are iterating, you are accessing using .body instead of .data. You need to be using .data. Also, you can eliminate restrauntsList array since you're using a .map()
3. Calling resolve() to return values.
You can't use resolve() to return value within Promise.all[] since you didn't create a promise using new Promise((resolve, reject) => { ... });. By default, a return within a promise will be a promise. so, a simple return will suffice. But if you want to be explicit, you can also return using Promise.resolve()
Making those changes,
return Promise.all(restrauntsListPromise).then(function (result) {
return {
restrauntsList: result.map(function (restraunts) {
return restraunts.data;
})
};
//or using Promise.resolve();
// return Promise.resolve({
// restrauntsList: result.map(function (restraunts) {
// return restraunts.data;
// })
// });
});
You are looking for
return Promise.all(restrauntsListPromise).then(function(result) { /*
^^^^^^ */
var contents = result.map(function(restaurants) {
return restaurants.body;
// ^^^^^^^^^^^^^^^^^^^^^^^^
});
return {restaurantsList: contents};
// ^^^^^^
});
You need to return the promise chain from the getRestrauntsList method, you should return the value from the map callback instead of using push on an array, and you will need to return from the then callback - there is no resolve function as you're not inside a new Promise constructor that you only need for callback APIs.

How to properly check and log http status code using promises and node.js?

I am new to JavaScript and very new to node.js framework, just started using it a few days ago. My apologies if my code is nonsensical, the whole idea of promises and callbacks is still sinking in. That being said my question is the following I am trying to figure out if certain request to websites are successful or cause an error based on the range of their status code response. I am working with an array of websites and what I've done so far is below, I do however get a TypeError: Cannot read property 'then' of undefined on my local machine with node.js installed and can't figure out why.
const sample = [
'http://www.google.com/',
'http://www.spotify.com/us/',
'http://twitter.com/',
'http://google.com/nothing'
]
const http = require('http')
const getStatusCodeResult = (website) => {
http.get(website, (res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
let statusCode = res.statusCode
error = statusCode >= 400 && statusCode <= 500 ? `error: ${website}`: null
if (error) {
reject(error)
} else if (statusCode >= 200 && statusCode <= 300) {
resolve(`Success: ${website}`)
}
}, 0)
})
})
}
// LOOP PROMISES
const getAllStatusCodeResult = (websites) => {
websites.forEach((website) => {
getStatusCodeResult(website)
.then((result) => {
console.log(result)
})
.catch(error => {
console.log('error', error)
})
})
}
getAllStatusCodeResult(sample)
Ideally I would want the result to be printed as the example below, but for now I am just using console.log to figure out if the code even works.
// Example Printout
{
success: ['https://www.google.com/', 'https://www.spotify.com/us/',
'https://twitter.com /' ],
error: [''http://google.com/nothing']
}
You mixed up the first two lines. The new Promise wrapper that gets you the value to return needs to be on the outside, and the http.get call should be inside its executor callback. Also you don't really need that timeout:
function getStatusCodeResult(website) {
return new Promise((resolve, reject) => {
http.get(website, (res) => {
let statusCode = res.statusCode,
error = statusCode >= 400 && statusCode <= 500 ? `error: ${website}`: null
if (error) {
reject(error)
} else if (statusCode >= 200 && statusCode <= 300) {
resolve(`Success: ${website}`)
}
})
})
}
Using util.promisify(), you can convert http.get() into a promise-based asynchronous method, but first there's some preparation to do since it does not follow the convention of callback(error, response) { ... }:
const http = require('http')
const { promisify } = require('util')
// define a custom promisified version of `http.get()`
http.get[promisify.custom] = (options) => new Promise(resolve => {
http.get(options, resolve)
});
// convert callback to promise
const httpGet = promisify(http.get)
async function getStatusCodeResult(website) {
const res = await httpGet(website)
const status = res.statusCode
const message = `${http.STATUS_CODES[status]}: ${website}`
if (status >= 400) {
throw message
} else {
return message
}
}
In addition, you can use http.STATUS_CODES to get the the appropriate message for each possible statusCode rather than returning a vague Error or Success.

Fetch Api javascript receive JSON when error

I post data to server using fetch:
fetch(url, {
method: 'post'
})
.then(status)
.then((data) => {
console.log(data);
}).catch((error) => {
console.log('error: ' + error);
});
});
This is status method
const status = (res) => {
console.log(res);
if(res.status >= 200 && res.status < 300) {
return Promise.resolve(res.json());
} else {
return Promise.reject(res.json());
}
}
If code is 200 then it works fine (I receive my JSON). But when it is not, I catch the error and log it. It shows Unauthorized but I expect that I receive my error JSON. Something like this
{
"status": 400,
"error": "pass is not corret"
}
How can I catch my Json error?
At this point I'd think that you'd just need to return the JSON from the response. Judging by your conditional though you're returning the JSON regardless?:
const status = (res) => {
console.log(res);
if (res.status >= 200 && res.status < 300) {
return res.json();
}
}
res.json() returns a Promise. When you call Promise.reject you can only pass it a reason for the rejection. Like a string, object or Error describing why you're rejecting.
I would change the code a bit to
const status = (res) => {
return new Promise((resolve, reject) => {
res.json().then((json) => {
if(res.status >= 200 && res.status < 300) {
resolve(json);
}
else {
reject(json)
}
});
});
}
Source:
MDN - Promise.resolve
MDN - Promise.reject

Fetch multiple promises, return only one

I have a list of urls I wish to fetch. All of these urls returns a json object with a property valid. But only one of the fetch promises has the magic valid property to true.
I have tried various combinations of url.forEach(...) and Promises.all([urls]).then(...). Currently my setup is:
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
var result;
urls.forEach(function (url) {
result = fetch(`${url}/${key}/validate`)
.then((response) => response.json())
.then((json) => {
if (json.license.valid) {
return json;
} else {
Promise.reject(json);
}
});
});
return result;
}
The above is not working because of the async promises. How can I iterate my urls and return when the first valid == true is hit?
Let me throw a nice compact entry into the mix
It uses Promise.all, however every inner Promise will catch any errors and simply resolve to false in such a case, therefore Promise.all will never reject - any fetch that completes, but does not have license.valid will also resolve to false
The array Promise.all resolves is further processed, filtering out the false values, and returning the first (which from the questions description should be the ONLY) valid JSON response
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
return Promise.all(urls.map(url =>
fetch(`${url}/${key}/validate`)
.then(response => response.json())
.then(json => json.license && json.license.valid && json)
.catch(error => false)
))
.then(results => results.filter(result => !!result)[0] || Promise.reject('no matches found'));
}
Note that it's impossible for validate to return the result (see here for why). But it can return a promise for the result.
What you want is similar to Promise.race, but not quite the same (Promise.race would reject if one of the fetch promises rejected prior to another one resolving with valid = true). So just create a promise and resolve it when you get the first resolution with valid being true:
export function validate(key) {
return new Promise((resolve, reject) => {
let completed = 0;
const total = urls.length;
urls.forEach(url => {
fetch(`${url}/${key}/validate`)
.then((response) => {
const json = response.json();
if (json.license.valid) {
resolve(json);
} else {
if (++completed === total) {
// None of them had valid = true
reject();
}
}
})
.catch(() => {
if (++completed === total) {
// None of them had valid = true
reject();
}
});
});
});
}
Note the handling for the failing case.
Note that it's possible to factor out those two completed checks if you like:
export function validate(key) {
return new Promise((resolve, reject) => {
let completed = 0;
const total = urls.length;
urls.forEach(url => {
fetch(`${url}/${key}/validate`)
.then((response) => {
const json = response.json();
if (json.license.valid) {
resolve(json);
}
})
.catch(() => {
// Do nothing, converts to a resolution with `undefined`
})
.then(() => {
// Because of the above, effectively a "finally" (which we
// may get on Promises at some point)
if (++completed === total) {
// None of them had valid = true.
// Note that we come here even if we've already
// resolved the promise -- but that's okay(ish), a
// promise's resolution can't be changed after it's
// settled, so this would be a no-op in that case
reject();
}
});
});
});
}
This can be done using SynJS. Here is a working example:
var SynJS = require('synjs');
var fetchUrl = require('fetch').fetchUrl;
function fetch(context,url) {
console.log('fetching started:', url);
var result = {};
fetchUrl(url, function(error, meta, body){
result.done = true;
result.body = body;
result.finalUrl = meta.finalUrl;
console.log('fetching finished:', url);
SynJS.resume(context);
} );
return result;
}
function myFetches(modules, urls) {
for(var i=0; i<urls.length; i++) {
var res = modules.fetch(_synjsContext, urls[i]);
SynJS.wait(res.done);
if(res.finalUrl.indexOf('github')>=0) {
console.log('found correct one!', urls[i]);
break;
}
}
};
var modules = {
SynJS: SynJS,
fetch: fetch,
};
const urls = [
'http://www.google.com',
'http://www.yahoo.com',
'http://www.github.com', // This is the valid one
'http://www.wikipedia.com'
];
SynJS.run(myFetches,null,modules,urls,function () {
console.log('done');
});
It would produce following output:
fetching started: http://www.google.com
fetching finished: http://www.google.com
fetching started: http://www.yahoo.com
fetching finished: http://www.yahoo.com
fetching started: http://www.github.com
fetching finished: http://www.github.com
found correct one! http://www.github.com
done
If you want to avoid the fact of testing each URL, you can use the following code.
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
return new Promise((resolve, reject) => {
function testUrl(url) {
fetch(`${url}/${key}/validate`)
.then((response) => response.json())
.then((json) => {
if (json.license.valid) {
resolve(json);
return;
}
if (urlIndex === urls.length) {
reject("No matches found...");
return;
}
testUrl(urls[urlIndex++]);
});
}
let urlIndex = 0;
if (!urls.length)
return reject("No urls to test...");
testUrl(urls[urlIndex++]);
});
}

Categories