Extract function from javascript promise (expressjs + neDB) - javascript

The following code is working and retrieving all users from my neDB-promisses:
const getUsers = (res) => {
db.find({})
.sort({ name: 1 })
.exec()
.then(
(content) => {
res.status(200).json(content);
},
(err) => {
res.status(400).json(err);
}
);
};
What I'm trying to do: optimize this piece of code in order to avoid future repetitions on CRUD functions, something like this:
...
.then(successFunctionCall, failureFunctionCall)
...
I thought about creating a separate module called successFunctionCall/failureFunctionCall but I need to call res inside it to set the response JSON and status code. Is that a better way to achieve this ?
Thank you.

You can curry the functions. When you use them as handlers, pass res, and get a new function that waits for the content or err:
const successFunctionCall = res => content =>
res.status(200).json(content);
const failureFunctionCall = res => err =>
res.status(400).json(err);
const getUsers = (res) => {
db.find({})
.sort({ name: 1 })
.exec()
.then(
successFunctionCall(res),
failureFunctionCall(res)
);
};

In order to avoid repetitions on CRUD functions, your concerns could be separated a little differently. Below is a basic idea of what I mean.
const usersDb = {} // your users db instance here
const findAllSortedBy = db => (...args) => db.find({}).sort(...args).exec()
const findAllUsersSortedBy = findAllSortedBy(usersDb)
const success = res => content => res.status(200).json(content)
const failure = res => err => res.status(400).json(err)
const getUsers = res => {
findAllUsersSortedBy({ name: 1 })
.then(success(res))
.catch(failure(res))
}
here is getUsers in context of an express route handler
const getUsers = (req, res) => {
findAllUsersSortedBy({ name: 1 })
.then(success(res))
.catch(failure(res))
}
router.get('/users', getUsers)

Related

how to make one function instead of 3 functions?

I noticed that the only thing that changing is the Product.find({type:''}) in the three functions so they all the same...
can I do one function do the same work ?
routes.js file :
mainRouter.get('/showChips',showChips);
mainRouter.get('/showJuices',showJuices);
mainRouter.get('/showSoftDrinks',showSoftDrinks);
controller.js file :
const showChips = async (req,res)=>{
const chipsItems = await Product.find({type:'chips'});
console.log(chipsItems);
res.json(chipsItems)
};
const showJuices = async (req,res)=>{
const juicesItems = await Product.find({type:'juices'});
console.log(juicesItems);
res.json(juicesItems)
};
const showSoftDrinks = async (req,res)=>{
const softDrinksItems = await Product.find({type:'Soft Drinks'});
console.log(softDrinksItems);
res.json(softDrinksItems)
};
You can make a higher-order function that returns a function that .finds a particular type:
const makeShowProduct = type => (req, res) => {
Product.find({ type })
.then(result => res.json(result))
.catch(handleErrors); // don't forget this part - don't create unhandled rejections
};
router
.get('/showChips', makeShowProduct('chips'))
.get('/showJuices', makeShowProduct('juices'))
.get('/showSoftDrinks', makeShowProduct('Soft Drinks'))

Merging various backend requests in the express res.send()

I'm trying to make several asynchronous backend calls to generate a JSON response in my express API. Because of the nature of the API, I have 3 requests that are being made that are dependent on each other in some way.
Request 1: Returns an Array of values that are used to make request 2. Each value will be used as a mapping for the remaining requests. That is to say, it will be a unique identifier used to map the response from the requests in Request 3.
Request 2 (Parallel Batch): A request is made using each value from the Array returned in request 1. Each of these returns a value to be used in each of the Request 3s. That is to say, it's a 1-to-1
Request 3 (Parallel Batch): This request takes the response from Request 2, and makes a 1-to-1 follow up request to get more data on that specific mapping (the id from request 1)
I would like the final data I send to the consumer to look like this:
{
id1: details1,
id2: details2,
id3: details3,
...
}
Here is the code I have so far...
app.get("/artists/:artist/albums", (req, res) => {
console.log("#############")
const artistName = req.params.artist
let response = {};
let s3Promise = s3.listAlbums(artistName)
let albumDetailsPromises = []
s3Promise
.then((data) => {
data.map((album) => {
// Each album name here will actually be used as the unique identifier for
// the final response
// Build an Array of promises that will first fetch the albumId, then use
// that album id to fetch the details on the album
albumDetailsPromises.push(
discogs.getAlbumId(artistName, album).then( // Returns a promise
({ data }) => {
let masterId = data.results[0].id
let recordName = data.results[0].title
// Storing the album name to carry as a unique id alongside the promise
return [album, discogs.getAlbumDetails(masterId) // Returns a promise ]
}
)
)
})
})
.then(() => {
// When all the albumIds have been fetched, there will still exist a promise in the
// second index of each element in the albumDetailsPromises array
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
namedPromises.map(
(album) => {
let albumName = album[0] // Unique Id
let albumDetailPromise = album[1]
// Resolving the albumDetailsPromise here, and storing the value on
// a response object that we intend to send as the express response
albumDetailPromise
.then(
({ data }) => {
response[albumName] = data
})
.catch(err => response[albumName] = err)
})
})
})
.catch((err) => console.log(err))
})
As of now, everything seems to be working as expected, I just can't seem to figure out how to "await" the response object being updated at the end of all these Promises. I've omitted res.send(response) from this example because it's not working, but that's of course my desired outcome.
Any advice is appreciated! New to javascript...
I would recommend rewriting this using async/await as it helps to reduce nesting. You can also extract the logic the get the album-details into a separate function, as this also increases the readability of the code. Something like this (this still needs error-handling, but it should give you a start):
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist;
const albumNames = await s3.listAlbums(artistName);
const result = {};
const albumDetailPromises = albumNames.map(albumName => requestAlbumDetails(discogs, artistName, albumName));
const resolvedAlbumDetails = await Promise.all(albumDetailPromises);
// map desired response structure
for(const albumDetail of resolvedAlbumDetails) {
result[albumDetail.albumName] = albumDetail.albumDetails;
}
res.json(result);
});
async function requestAlbumDetails(service, artistName, albumName) {
const albumInfo = await service.getAlbumId(artistName, albumName);
const masterId = albumInfo.results[0].id;
const albumDetails = await service.getAlbumDetails(masterId);
return { albumName, albumDetails };
}
To answer your question how you could do it with your code:
You'd need to wait for all details to be fulfilled using another Promise.all call and then just send the response in the then-handler:
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
const detailsPromises = namedPromises.map(
(album) => {
let albumName = album[0];
let albumDetailPromise = album[1];
return albumDetailPromise
.then(({ data }) => {
response[albumName] = data;
})
.catch(err => response[albumName] = err);
});
return Promise.all(detailsPromises)
.then(() => res.json(response));
})
Refactored using async/await...
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist
let response = {};
let albums = await s3.listAlbums(artistName)
const promises = albums.map(async (album) => {
let result = await discogs.getAlbumId(artistName, album)
try {
let masterId = result.data.results[0].id
let tempRes = await discogs.getAlbumDetails(masterId)
return [album, tempRes.data]
} catch (error) {
return [album, { "msg": error.message }]
}
})
responses = await Promise.all(promises)
responses.map(data => { response[data[0]] = data[1] })
res.send(response)
})

async await mongodb find filter is not working, how can i make this work?

i want the code to not return everything except a specific value in the database objects that are returned but even after putting in the filter it still return the unwanted value. this is the code that i am using. The field that I am trying to remove from the objects is "ss". the filter function is written similar to what the MongoDB documentation has told me to do but i am missing something.
client side
this.blocks = await CatService.getAllBlocks()
static getAllBlocks() {
let result = new Promise ((resolve, reject) => {
axios
.get(`/api/cats/read-all`)
.then((res) => {
const data = res.data
console.log('RETURNED DATA:', data)
resolve(
data.map((block) => ({
...block,
createdAt: new Date(block.createdAt)
}))
)
})
.catch((err)=> { reject(err) })
})
return result
}
Server Code
router.get('/read-all', async (req, res) => {
const blocks = await loadBlocksCollection()
let retrievedData = await blocks.find(
{},
{ "ss": 0 }
).toArray()
res.send(retrievedData)
})
async function loadBlocksCollection() {
const uri = process.env.MONGO_URI
const db_name = process.env.DB || 'db_name'
const c_name = 'blocks'
const client = await mongodb.MongoClient.connect(
uri,
{
useNewUrlParser: true,
useUnifiedTopology: true
}
)
return client.db(db_name).collection(c_name)
}
this is not correct syntax:
let retrievedData = await blocks.find(
{},
{ "ss": 0 }
).toArray()
The correct way to filter Data is like this:
let retrievedData = await blocks.find(
{}
).project({ ss: 0 }).toArray()

How to send res.json() in Express JS from a for loop

I am trying to send a json response when an API call is triggered from front-end, I'm not able to send the res.json() when I am getting the data from a for loop. Where I am writing a query to search in multiple Tables. I am using RethinkDB.
I want res.json() to send data after the query, But I don't understand what mistake I am doing. :(
Thanks in advence
zeasts
Here is the following code and Fiddle Link too.
const express = require("express");
const router = express.Router();
const moment = require('moment');
const r = require('rethinkdb');
const tableNameDB = ['assets', 'alerts', 'destinations']
router.post('/', (req, res, next) => {
let resData = []
let searchValue = req.body.searchValue,
tableName = req.body.tableName;
newCallForSearch(res, searchValue, resData)
})
function newCallForSearch (res, searchValue, resData){
let anArray = ['captain']
for(var i = 0; i<tableNameDB.length; i++){
let tabName = tableNameDB[i]
r.table(tableNameDB[i]).filter(function(doc) {
return doc.coerceTo('string').match(searchValue);
}).run(rethink_conn, (err, cur) => {
// console.log(cur)
if (err) {
return 0
} else {
cur.toArray((err, result) => {
if (err) {
return 0
} else if (result) {
let Results = []
Results = Object.values(result).slice(0,10)
var newResults = Results.map(function() {
resData = Object.assign({'tableName': tabName},{'data' : result});
anArray.push(resData)
})
}
})
}
})
}
res.status(200);
res.json(anArray);
}
module.exports = router;
RethinkDb is a functional database and so using it in a functional way will yield the least resistance. We can accomplish more by writing less code.
You can use Promise.all to run many subqueries and pass the result to res.send -
const search = (table = "", query = "", limit = 10) =>
r.table(table)
.filter(doc => doc.coerceTo("string").match(query))
.toArray()
.slice(0, limit)
.do(data => ({ table, data }))
const tables =
['assets', 'alerts', 'destinations']
const subqueries =
tables.map(t => search(t, "foo").run(rethink_conn)) // <-- search each table
Promise.all(subqueries) // <-- run all subqueries
.then(result => { // <-- handle success
res.status(200)
res.json(result)
})
.catch(e => { // <-- handle failure
res.status(500)
res.send(e.message)
})
Or use .union to produce a single query -
const search = (table = "", query = "", limit = 10) =>
r.table(table)
.filter(doc => doc.coerceTo("string").match(query))
.toArray()
.slice(0, limit)
.do(data => ({ table, data }))
const searchAll = (tables = [], query = "", limit = 10) =>
tables.reduce
( (r, t) => r.union(search(t, query, limit)) // <-- union
, r.expr([]) // <-- if no tables are supplied, return empty result
)
const tables =
['assets', 'alerts', 'destinations']
searchAll(tables, "foo") // <-- single rethink expr
.run(rethink_conn) // <-- run returns a promise
.then(result => { // <-- handle success
res.status(200)
res.json(result)
})
.catch(e => { // <-- handle failure
res.status(500)
res.send(e.message)
})
I should remark on the proposed use of filter in your original post -
.filter(doc => doc.coerceTo("string").match(query))
This is quick but it is also sloppy. It matches query against any of docs values, but also the doc's keys. And if doc is a complex nested document, it matches them too. User beware.

Promises resolving in wrong order

I am playing with promises and modified a script from Medium.
When I run the script it prompts for the code then displays the json data before I can input a value. I then input a value without the prompt for the script to exit.
How can I get the input before the API call works?
'use strict'
const request = require('request')
const readline = require('readline')
let userDetails
const getInput = prompt => new Promise( resolve => {
const io = { input: process.stdin, output: process.stdout }
const read = readline.createInterface(io)
read.question(`${prompt}: `, data => {
console.log(data)
read.close()
resolve(data)
})
})
const getData = () => new Promise( (resolve, reject) => {
const options = {
url: 'https://api.github.com/users/marktyers',
headers: {
'User-Agent': 'request'
}
}
// Do async job
request.get(options, (err, resp, body) => {
if (err) reject(err)
else resolve(JSON.parse(body))
})
})
function main() {
const GitHubAPICall = getData()
const getBase = getInput('input base currency')
GitHubAPICall
.then(result => {
userDetails = result
console.log('Initialized user details')
console.log(userDetails)
}).then(getBase)
.catch(err => console.log(err))
}
main()
In your main function, you can do it like that:
function main() {
const GitHubAPICall = getData; // WITHOUT ()
const getBase = getInput; // Those 2 declarations are useless, btw
GitHubAPICall()
.then(result => {
userDetails = result
console.log('Initialized user details')
console.log(userDetails)
})
.then(() => getBase())
.then(data => // Do something with the data returned by 'getInput')
.catch(err => console.log(err))
}

Categories