Make DRY code from Async multiple functions inside array of Promise.all - javascript

Hello the following code is working. However, I was wondering if there is a way to not repeat the function findMovie in the array which is asynchronous multiple times as I'm currently doing.
var request = require('request-promise');
function findMovie(title){
return request(`http://www.omdbapi.com/?t=${title}`)
.then(res=>{
var movie= JSON.parse(res)
return [movie["Title"], movie["Year"],movie["Genre"]]
})
}
function loadInitialData(movies){
return Promise.all(movies)
.then((response)=>{
response.forEach((movie)=>{
console.log(movie[0])
})
})
}
var movies= [findMovie("jason bourne"), findMovie("The Matrix"), findMovie("titanic")];
// Above is the function findMovie being written multiple times to make it work...
loadInitialData(movies);

You can use map to run a function on every element of an array and return a new array. So you can have a list of strings, and map it to a list of movies.
var movies= ["jason bourne", "The Matrix", "titanic"].map(function (movie) {
return findMovie(movie);
});
Now because your function takes a single parameter, you can just pass the function name to map, which neatens it up further
var movies= ["jason bourne", "The Matrix", "titanic"].map(findMovie)

This doesn't have anything to do with your code and everything to do with whether the API you're calling supports batch requests. If it did, then you could pass all the titles at once in a single request, get a JSON object back with an array of movies in it. Again, only if it support that.
If it's any consolation, all the requests that your findMovie() function are doing will be done in parallel, so they'll be faster than making sequential requests for each movie.
For completeness, this isn't really what DRY (Don't Repeat Yourself) means. DRY refers to not writing the same code over and over when it could be put into a function. If your code wasn't DRY, you wouldn't have the findMovie() and would instead have multiple separate calls to request() in your main code body.

Here's probably the way I'd write it
const request = require('request-promise')
const findMovie = title =>
request(`http://www.omdbapi.com/?t=${title}`)
.then(JSON.parse)
.then(({Title, Year, Genre}) => [Title, Year, Genre])
const findMovies = titles =>
Promise.all(titles.map(findMovie))
findMovies(["jason bourne", "The Matrix", "titanic"])
.then(movies => console.log(movies),
err => console.error(err.message))

Related

How to use if else loop in Dialogflow for showing results

I have Javascript code for Dialogflow doing project on Google Actions. For this code if answer is in database means it will answer otherwise it is exiting out of app. So, I want to use else loop for this code plz help me
function handleCompanyDetails(agent){
const RegNo = agent.parameters.RegNo;
var ref8 = admin.database().ref().child("Table/");
var query8 = ref8.orderByChild("RegNo").equalTo(RegNo);
return query8.once("value")
.then(function(snapshot) {
snapshot.forEach(function(child) {
if( !snapshot.exists() ){
// There are no results, say so
agent.add("There are no results for that account.");
} else {
// ... Do something with the data
agent.add(`The student placed in ` + child.val().CompanyName);
}
});
});
}
While you can use a loop to show results, there are a few problems with how you've done to, and possibly even with what you're trying to return.
First - Dialogflow requires you to return a Promise from any function that makes an asynchronous call, such as a call to the Firebase database. You're currently using the callback method. You should switch to using once() that returns a Promise instead, so that might look something like this:
return query8.once("value")
.then( snapshot => {
// working with snapshot goes here
})
.catch( err => {
console.error(err);
agent.add("There was a problem.");
});
The second is how you work with snapshot itself. If you're expecting multiple results, you should be aware that you can only call agent.add() with a text message twice and one basic card. If you want multiple cards, you may want to use a list or carousel instead.
If you are expecting only one response indexed off the RegNo, which it looks like you may be, then you should just include that as part of the path and get the value of the snapshot. You wouldn't need a loop in this case.
Update based on updated code.
As you noted, you're not sending anything if there are no results, so the Action exits with an error.
The easiest way to do this is to use snapshot.exists() to check if there are any results in the snapshot. If there aren't any, then you can return an error. That might look something like
return query8.once("value")
.then(function(snapshot) {
if( !snapshot.exists() ){
// There are no results, say so
agent.add("There are no results for that account.");
} else {
// ... Do something with the data
}
});
// ...
If you do have results, you still have the issue that you may be sending back too many replies. You can only have one agent.add() with a message to be spoken (or two, at the most, but don't do that) and only one Card, unless you use a List or Carousel. So it would be better for you to build that message inside the loop.
Use .then() while doing operations on snapshot. Because .once() only triggers one time so if data is available meaning.then() will be executed and you can exit it using .catch(). check the code below.
function handleCompanyDetails(agent){
const RegNo = agent.parameters.RegNo;
var ref8 = admin.database().ref().child("Table/");
var query8 = ref8.orderByChild("RegNo").equalTo(RegNo);
return query8.once("value")
.then(function(snapshot) {
snapshot.forEach(function(child) {
agent.add(`The student placed in ` + child.val().CompanyName);
agent.add(new Card({
title: ` Name:${child.val().Studentname}
Reg No: ${child.val().RegNo}
Offer Date: ${child.val().OfferDate} `,
imageUrl: '',
text: `Thanks for using 💁\n ${child.val().FirstName} 💁`,
buttonText: '.com'
})
})
})
.catch( // throw some error)
}
you can read more here, https://firebase.google.com/docs/database/web/read-and-write

Pg-promise - Reusable queries that may participate in a transaction/task

I am trying to achieve a reuse pattern where query functions may receive a parameter trx=tx/task if they are participating in an existing transaction/task, and reuse that tx/task context... otherwise if trx=undefined passed in, creates a new task for the queries. The idea is for the function to be agnostic of whether it is being used singularly, or is participating in a higher-order block transaction.
What I desire (ideally) is a promise-function that will return a task context so that I can write clean like the following (which doesn't work):
async function trxRunQueries(trx:ITask<any>|undefined = undefined):Promise<any[]>
{
if(!trx)
trx=await db.task(); // if !trx then create new task context from db
const dataset1 = await trx.many(`select * from tableA`);
const dataset2 = await trx.many(`select * from tableB`);
return [dataset1,dataset2];
}
However seems like db.task() needs to execute the contextual queries in the cb parameter, but it leaves me hanging & wondering how I can achieve the desired pattern without writing the code out twice -- once with db.task(trx => ) wrapper, and another executing trx.many(...) directly.
I was wondering if it is ok to do something hacky, like below, to achieve this pattern of participating-optionally-in-a-transaction, and will it work (or is it really not a recommended way of doing things) -- or is there a better way that I am not thinking of?
async function runQueries(trx:ITask<any>):Promise<any[]>
{
const dataset1 = await trx.many(`select * from tableA`);
const dataset2 = await trx.many(`select * from tableB`);
return [dataset1,dataset2];
}
async function trxRunQueries(trx:ITask<any>|undefined = undefined ):Promise<any[]>
{
let result:any[]=[];
try {
// If trx not passed in the create task context
if(!trx)
await db.task(async trx => {result=await runQueries(trx)})
else
result=await runQueries(trx);
return result;
}
catch (err) {
throw(err);
}
}
Such pattern is implemented by pg-promise-demo, which uses event extend to extend the database protocol with context-agnostic entity repositories.

Promise inside promise don't wait Axios finishing

I have a trouble. I receive many objects where I mapping them and I make a external consult using axios and save the return, let's the code:
let savedClients = Object.entries(documents).map(personDocument => {
let [person, document] = personDocument
documentFormated = document
documentNumbers = document.replace(/\D/g, '')
return ConsultDocuments.getResponse(documentNumbers).then(resultScore => { // Calling the axios
const info = { ...resultScore }
return Save.saveClient(info)
})
})
Promise.all(savedClients).then(results => {
console.log(results) // Come only one document, repeted with the total documents passed in map
})
The problem is when it realized the all map first and then make the consults with only the last result many time (the total of documents passed)
This code is legacy and use async/await don't work (serious, if i don't stay here)
I'am tried N ways to make this, and with the libary Q(), it's make the map in correcty order but it's doesn't wait the axios, and all results come with "pending"
Thanks!

How do I implement a find-or-create pattern in firestore

The firestore api has me a little mixed up in trying to have a repeatable pattern for find-or-create style functions. I'd like the canonical version to look like this:
// returns a promise resolving to a DocumentSnapshot (I think??)
function findOrCreateMyObject(myObject) {
return findMyObject(myObject.identifier).then(documentSnapshot => {
return (documentSnapshot)? documentSnapshot : createMyObject(myObject);
});
};
I'm not sure if DocumentSnapshot is the appropriate return from this, but I figure the caller may want to inspect or update the result, like this:
return findOrCreateMyObject({ identifier:'foo' }).then(documentSnapshot => {
console.log(documentSnapshot.data.someProperty);
return documentSnapshot.ref.update({ someProperty:'bar' });
});
Assuming I am right about that (please tell me if not), it means that both the find and create functions must return a DocumentSnapshot. This is easy enough for the find...
function findMyObject(identifier) {
let query = db.collection('my-object-collection').where('identifier','=='identifier);
return query.get().then(querySnapshot => {
return (querySnapshot.docs.length)? querySnapshot.docs[0] : null;
});
}
...but rather awkward for the create, and the the gist of my problem. I'd want to write create like this....
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject);
}
But I cannot because DocumentReference set() resolves to a "non-null promise containing void". Void? I must read back the object I just wrote in order to get a reference to it? In other words, my idealized create needs to be rewritten to be slower and clunkier...
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject).then(() => {
// must we query the thing just written?
return findMyObject(myObject.identifier); // frowny face
});
}
This makes my generic create longer (and unnecessarily so when doing just a create). Please tell me:
is DocumentSnapshot the right "currency" for these functions to traffic in?
Am I stuck with a set() and then another query when creating the new object?
Thanks!
EDIT As an example of where I would apply this, say I have customers, uniquely identified by email, and they have a status: 'gold', 'silver' or 'bronze'. My CRM system decides that someone identifying himself as doug#stevenson.com deserves 'silver' status. We don't know at this point wither Mr. Stevenson is a current customer, so we do a find-or-create...
findOrCreateCustomer({ email:'doug#stevenson.com' }).then(customer => {
customer.update({ status:'silver' });
});
I wouldn't just create, because the customer might exist. I wouldn't just update, because the customer might not exist, or might not meet some other criterion for the update.

How to wait until multiple EventEmitter instances have emitted same event in Node.js?

I'm working on a Node.js module/utility which will allow me to scaffold some directories/files. Long story short, right now I have main function which looks something like this:
util.scaffold("FileName")
This "scaffold" method returns an EventEmitter instance, so, when using this method I can do something like this:
util.scaffold("Name")
.on("done", paths => console.log(paths)
In other words, when all the files are created, the event "done" will be emitted with all the paths of the scaffolded files.
Everything good so far.
Right now, I'm trying to do some tests and benchmarks with this method, and I'm trying to find a way to perform some operations (assertions, logs, etc) after this "scaffold" method has been called multiple times with a different "name" argument. For example:
const names = ["Name1", "Name2", "Name3"]
const emitters = names.map(name => {
return util.scaffold(name)
})
If I was returning a Promise instead of an EventEmitter, I know that I could do something like this:
Promise.all(promises).then(()=> {
//perform assertions, logs, etc
})
However, I'm not sure how can I do the equivalent using EventEmitters. In other words, I need to wait until all these emitters have emitted this same event (i.e. "done") and then perform another operation.
Any ideas/suggestions how to accomplish this?
Thanks in advance.
With promise.all you have a unique information when "everything" is done.
Of course that is when all Promises inside are fullfiled/rejected.
If you have an EventEmitter the information when "everything" is done can not be stored inside your EventEmitter logic because it doesn't know where or how often the event is emmited.
So first solution would be to manage an external state "everything-done" and when this changes to true you perform the other operation.
So like promise.all you have to wrap around it.
The second approach i could imagine is a factory where you build your EventEmitters that keeps track of the instances. Then this factory could provide the information whether all instances have been fired. But this approach could fail on many levels: One Instance->many Calls; One Instance->no Call; ...
just my 5 cent and i would be happy to see another solution
The simplest approach, as mentioned by others, is to return promises instead of EventEmitter instances. However, pursuant to your question, you can write your callback for the done event as follows:
const names = ['Name1', 'Name2', 'Name3']
let count = 0
util.scaffold('Name').on('done', (paths) => {
count += 1
if (count < names.length) {
// There is unfinished scaffolding
} else {
// All scaffolding complete
}
})
I ended up doing what #theGleep suggested and wrapping each of those emitters inside a Promise, like this:
const names = ["Name1", "Name2", "Name3"]
const promises = names.map(name => {
return new Promise((resolve) => {
util.scaffold(name).on("done", paths => {
resolve(paths)})
})
})
// and then
Promise.all(promises).then(result => {
// more operations
})
It seems to be doing what I need so far, so I'll just use this for now. Thanks everyone for your feedback :)

Categories

Resources