i want to do http request using fetch(). The request is more than one that from same domain (it's just different endpoint).
https://api.banghasan.com/quran/format/json/cari/${keyword}/bahasa/id/mulai/0/limit/100
https://api.banghasan.com/quran/format/json/surat/${chapter}/ayat/${verse}
https://api.banghasan.com/quran/format/json/catatan/${num}
I made my code like this:
Get the first data(number of chapter and verse):
static getData(keyword) {
return fetch(`https://api.banghasan.com/quran/format/json/cari/${keyword}/bahasa/id/mulai/0/limit/100`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.reject(`The "${keyword}" keyword is not found`);
} else {
return Promise.reject(`Something Wrong`)
}
})
.catch(error => {
return Promise.reject(error);
})
}
If getData return `resolve', there are the number of chapter and verses
Then, get the verses:
static async getAyat(surat, ayat) {
return fetch(`https://api.banghasan.com/quran/format/json/surat/${surat}/ayat/${ayat}`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.resolve(rj.ayat.data);
} else {
return Promise.reject('Terjadi kesalahan')
}
})
.catch(error => {
return Promise.reject(error);
})
}
Last, get Notes, if the verse has something to explain
static getNote(num) {
return fetch(`https://api.banghasan.com/quran/format/json/catatan/${num}`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.resolve(rj.catatan.teks)
} else {
return Promise.reject('Terjadi kesalahan')
}
})
.catch(error => {
return Promise.reject(error);
})
}
The code is works properly. I just wanna know, is there simple way to write it?
Use a function. Ask yourself which parts are the same versus what is different, then take the parts that are different and make them parameters.
In your case, here's what's different:
The arguments to the function
The URL generation
The data you extract from the response
So here's how you can create a function to encapsulate those differences:
const baseUrl = 'https://api.banghasan.com/quran/format/json';
const returnAllData = data => data;
function createFetchMethod(urlBuilder, dataExtractor = returnAllData) {
return async (...params) => {
const url = urlBuilder(...params);
const response = await fetch(`${baseUrl}${url}`);
if (!response.ok) {
throw new Error("Something's wrong");
}
const json = await response.json();
if (json.status !== 'ok') {
throw new Error("Something's wrong");
}
return dataExtractor(json);
}
}
The way you'd use this is to create your methods like this:
const getData = createFetchMethod(
keyword => `/cari/${keyword}/bahasa/id/mulai/0/limit/100`
);
const getAyat = createFetchMethod(
(surat, ayat) => `/surat/${surat}/ayat/${ayat}`,
json => json.ayat.data
);
const getNote = createFetchMethod(
num => `/catatan/${num}`,
json => json.catatan.teks
);
These can now be called as before, only all the error handling is encapsulated. You can further customize by adding more parameters.
Note that one potential problem with your URL building code is if the parameters being injected aren't URL-safe, you need to call encodeURIComponent for them to escape special characters.
Related
I'm a bit new to coding in a JS/TS way and I just wanted to know the best practice when it comes to returning and handling void and values that come from a fetch. In short, I'd like to use fetch to retrieve a JSON file somewhere, extract some relevant info, and return a dict.
async function doSomething() {
const response = fetch(...)
.then(response =>
response.json().then(data => ({
data: data,
response: response.ok
}))
)
.then(data => {
if (!data.response) {
console.warn("Response was not ok...")
return null
} else if (data.data["results"] == 0) {
console.info("Returned no results")
return null
} else {
const returned = {
name: data.data["stuff"],
thing: data.data["other"]
}
return returned
}
})
.catch(error => console.error(`${error}`))
return response
TS returns an error saying that Property 'name' does not exist on type 'void | {...} when I want to use the values of the dict. The error, I completely understand. When using the function I try to do type checking to handle when it returns void, but it still raises an error for something like this:
const request = await getQuery("movie", query)
if (request === undefined || request === null) return "I wasn't able to find your query."
const data = {
name: request.name
}
}
I'm just wondering what's the best way to write and handle void, and using the dict values.
I wouldn't have the function do too much. Have it concentrate on returning some data, and let the function that calls it deal with how that data is going to be used.
If you're going to use async/await you should be consistent. Mixing async with then doesn't make a lot of sense. This way you're either going to return some data, or throw an error.
async function getData(endpoint) {
try {
const response = await fetch(endpoint);
if (response.ok) {
const { data } = await response.json();
return data;
} else {
throw new Error('Response error.');
return undefined;
}
} catch (err) {
console.warn(err);
}
}
async function main() {
const endpoint = 'http://example.com';
const data = await getData(endpoint);
if (data && data.results) {
console.log(data.results.name);
} else {
console.warn('No results');
}
}
main();
I have got this Node.JS snippet and would like to write it as a module, so I can use recaptcha in different parts of my system.
This is how it currently looks like:
app.post('/register_user', (req, res) => {
const secret_key = process.env.RECAPTCHA_SECRET;
const token = req.body.recaptcha;
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
fetch(url, { method: "post",})
.then((response) => response.json())
.then((google_response) => {
if (google_response.success == true) {
res.format({'text/html': () => res.redirect(303, '/register'),})
} else {
return res.send({ response: "Failed" });
}
})
.catch((error) => {
return res.json({ error });
});
})
I have tried to write the following module which works absolutely great, but I have absolute no idea about how to call it from the app.post, since I always get undefined as return:
import fetch from 'node-fetch';
export function fetch_out(url, timeout = 7000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), timeout)
)
]);
}
export async function checkRecaptcha(token, secret_key){
const url = "https://www.google.com/recaptcha/api/siteverify?secret=" + secret_key + "&response=" + token;
try{
const response = await fetch_out(url, 1000);
const google_response = await response.json();
}catch(error){
return error;
}
return google_response;
}
Any help would be appreciated! Thanks!
You could make this method reusable by removing the framework actions that need to happen and only return if the validation was successful or not. This way, it will be reusable in another project that doesn't use a specific framework.
Example module;
export async function checkRecaptcha(token, secret_key) {
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
const response = await fetch(url, { method: "post",});
if (!response.ok) return false;
const json = await response.json();
if (!json.success) return false;
return true;
}
Usage:
import { checkRecaptcha } from "./some-file-name";
app.post('/register_user', async (req, res) => {
const isHuman = await checkRecaptcha(req.body.recaptcha, process.env.RECAPTCHA_SECRET);
if (!isHuman) {
return res.send({ response: "Failed" });
}
return res.format({'text/html': () => res.redirect(303, '/register'),});
});
If you specifically want to call an action after the validation, you can also use successful and error callbacks.
Below you can see my first attempt at creating a service-worker and the problem with this code is that it never returns cached response since cache.match(request) in addUrlToCache function is always returning undefined. Does anyone have any ideas as to why it's not finding cached requests?
import API from 'top-secret'
const PHOTOS_CACHE = 'photos-cache'
const OBJECTS_CACHE = 'objects-cache'
const urlCacheData = [
{
cacheKey: OBJECTS_CACHE,
url: API.apiUrlGetObjects
},
{
cacheKey: PHOTOS_CACHE,
url: API.apiUrlGetPhotos
}
]
function addUrlToCache (request, cacheKey) {
return caches
.open(cacheKey)
.then(cache => cache.match(request))
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse
}
return fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response))
return response.clone()
})
})
}
function clearCache () {
return caches.keys().then(cacheNames => {
const promisesToDeleteCache = cacheNames.map(cacheName =>
caches.delete(cacheName)
)
return Promise.all(promisesToDeleteCache)
})
}
self.addEventListener('activate', event => {
event.waitUntil(clearCache())
})
self.addEventListener('fetch', event => {
const urlToCache = urlCacheData.find(item =>
event.request.url.includes(item.url)
)
if (urlToCache) {
event.respondWith(
addUrlToCache(event.request, urlToCache.cacheKey)
)
}
})
After you fetch request in your addUrltoCache function replace
fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response))
return response.clone()
})
with,
return fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response.clone()))
return response;
})
because you should clone first and then return the response.In your code you have already used your response to put value in cache.
I've just figured out that the problem was that I had JSONP requests with random callback values like bla-bla/api?callback=jsonp_randomNumber, so url would be different every time I make a request because of the random number thing, that's why cache.match check wouldn't work.
I fixed it by hardcoding callback value in the config of the jsonp library that I used (jsonp-fetch in my case).
I am making a request which returns paged data, so I want to call a function recursively until I have all of the pages of data, like so:
router.get("/", (req, res) => {
doRequest(1, [])
.then(result => {
res.status(200).send(result);
})
.catch(error => {
console.error(error);
res.status(500).send("Error from request");
});
});
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
My route is returning immediately with Cannot read property 'then' of undefined, so doRequest() is apparently returning undefined right away, rather than returning the list when it is ready. I'm new to promises so I'm sure I'm missing something pretty simple.
Change the doRequest function to
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
return request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
so it can return the request, its actually returns nothing (undefined)
Indeed doRequest is returning 'undefined' since the function does not return anything.
The return statements that you have there belong to the predicates of the 'then' and 'catch', but you have none in the function doRequest.
You should return the result of 'request' which should be a promise, so your code should look like this
function doRequest(page, list){
let options = {
uri : "www.example.com",
qs : { "page" : page }
};
return request(options)
.then((results) => {
list.push(results);
if(page === 2){
return Promise.resolve(list);
} else {
return doRequest(++page, list);
}
})
.catch((error) => {
return Promise.reject(error);
});
}
besides that I don't think is a good approach to handle this recursively, but this is probably another discussion.
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++]);
});
}