Defer an async service call until promise is resolved - javascript

I want to create an array of Twilio API calls to be resolved later in Promise.all.
TwilioClient.messages.create(payload) is getting called immediately inside the returned promise (A). How can I defer until the Promise.all chain resolves it (B)?
let twilioTexts = [];
if (twilioNumbers) {
twilioTexts = twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
// (A) ==> Twilio is sending messages here upon assigning to variable `twilioTexts`.
}
await Promise.all([
// do other stuff
...twilioTexts, // (B) ==> instead, Twilio should make all calls here
]);
...
function getTwilioText(mailTokens, phoneNumber) {
return new Promise((resolve, reject) => {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
TwilioClient.messages.create(payload);
});
}
Edit:
await Promise.all([
// do other async stuff in parallel
// check if twilioNumbers then spread map into Promise.all array
twilioNumbers && ...twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber)),
]);
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}

Your second example is almost there, but Promise.all works on an array of promises. The twilioNumbers &&... line produces an array of promises, so you've got an array of promises inside another array, which won't work.
So you can use the spread operator:
await Promise.all([
// do other stuff
...(twilioNumbers
? twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber))
: []
),
]);
...
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
At this point, it's a bit verbose, so you might want to factor that part out:
await Promise.all([
// do other stuff
getTwilioTexts(twilioNumbers, mailTokens),
]);
...
// returns a promise for an array
async function getTwilioTexts(numbers, mailTokens) {
return numbers
? Promise.all(numbers.map(num => getTwilioText(mailTokens, num)))
: [];
}
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}

You seem to be looking for
const otherPromises = // do other stuff, *triggered first*
const twilioTexts = (twilioNumbers || []).map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
await Promise.all([
...otherPromises,
...twilioTexts,
]);
Ultimately, it shouldn't matter though as you want to run the other stuff concurrently with sending texts anyway.

Related

Empty result in my response to front-end, but in console is returning OK

So, I have a REST API, and I want to return some data to my front-end.
So I have this code:
.then(async result => {
// return result.rows
return result.rows.map(async res1 => {
res1.datasProc = [];
const getDatt = await getDatasProcedimentos(res1.GUIA_SENHA);
res1.datasProc.push(...getDatt);
console.log(res1);
return res1;
})
})
In the line 2 return result.rows, its ok. But I want to add some data at this object with map method. This data come from line 6 (SQL query). I already test the query function and its working fine. At line 8, my res1 Object, contain all the data that I want, but the result is empty.
My console.log(res1)
{
NM_PESSOA_FISICA: 'name of someone',
NR_CPF: 'document of someone',
GUIA_SENHA: 'id of company',
PROCEDIMENTO: 'procedure of someone in company',
DATA: 'timestamp',
DS_PROCEDIMENTO: 'description of procedure',
datasProc: [
{ 'timestamp1' },
{ 'timestamp2' },
{ 'timestamp3' }
]
} ... and so on
My response to front-end (Prompt by insomnia using the endpoint)
result = [
{},
{},
{},
{},
{},
{},
// ... and so on
]
I've tried in another ways, like assign the map result into variable and returning that, but the asynchronous block is returning first and after doing the map.
Edit 1: My controller to send the data is in a try-catch block:
try {
const guiasPorPeriodo = await guiaDAO.getGuiasPorPeriodo(data_filtro_inicial, data_filtro_final, idClinic);
if (guiasPorPeriodo) {
return res.json({
"resultado": guiasPorPeriodo,
"status": "success",
"message": "enviado com sucesso"
});
} else {
throw new Error('Ocorreu algum erro ao consultar as guias neste perĂ­odo.')
}
} catch (err) {
return res.status(400).json({
"status": "fail",
"error message": `${err.name}: ${err.message}`
});
}
language: pt-BR
This statement:
return result.rows.map(async res1 => {
will return an array of Promises, not resulting objects. To make sure your async function would return array of actual results I would try this:
.then(result => {
// return result.rows
return Promise.all(result.rows.map(async res1 => {
res1.datasProc = [];
const getDatt = await getDatasProcedimentos(res1.GUIA_SENHA);
res1.datasProc.push(...getDatt);
console.log(res1);
return res1;
}))
})
The mapper function is declared as async so technically it returns a promise.
And a function which is not declared as async but returns a Promise still can be called with await.
The problem is your map with async/await. A function with await will return a promise, and you need to wait for the promise to get resolved. Try with Promise.all, that should wait until all your promises are resolved.
.then(async result => {
// return result.rows
return Promise.all(result.rows.map(async res1 => {
res1.datasProc = [];
const getDatt = await getDatasProcedimentos(res1.GUIA_SENHA);
res1.datasProc.push(...getDatt);
console.log(res1);
return res1;
}))
})
Another way could be to use a for loop. That works better with async/await.

promises with search function

I have an array of objects, there is a function to search for a book by name, I want to use the search function through promises, after a successful search, the convert function should be executed. when I do this, I get an empty array
let book1 = {
name: "wind",
author: 'smith',
size: 314,
};
let book2 = {
name: "moon",
author: 'wild',
size: 421,
};
let book3 = {
name: "sun",
author: 'jacob',
size: 510,
};
let books1 = [book1, book2, book3];
let books = [];
function searchName(search) {
return new Promise(function(resolve, reject) {
books1 = books1.filter(function(elem) {
elem.name.includes(search)
})
})
};
function convert() {
return books = books1.map(item => item.name);
};
searchName("wind").then(convert);
console.log(books);
Add return statement in book1.filter to get filtered array
Call resolve callback in Promise body
If you do not catch rejected Promise you can omit reject callback in Promise executor's arguments
console.log(books) at the end of your example will always return an empty array because Promise will be executed after the console.log. Read about Microtasks, here is a good explanation https://javascript.info/microtask-queue
Try this code:
function searchName(search) {
return new Promise(function (resolve) {
books1 = books1.filter(function (elem) {
return elem.name.includes(search);
});
if (books1.length) {
resolve();
}
});
}
function convert() {
books = books1.map(item => item.name);
console.log(books);
return books;
}
searchName('wind').then(convert);
You're using Promises incorrectly in a variety of ways -
const books = // use const for bindings that will not be reassigned
[ { name: "wind" // objects can be declared in an array, if you wish
, author: 'smith' // <-- book1
, size: 314,
}
, { name: "moon" // <-- book2
, author: 'wild'
, size: 421
}
, { name: "sun" // <-- book3
, author: 'jacob'
, size: 510
}
]
const searchBooks = (query = "") =>
new Promise((resolve, _) => // <-- reject unused
resolve(books.filter(b => // call resolve, don't reassign books
b.name.includes(query)
))
)
const convert = (books = []) => // receives argument
books.map(b => b.name) // return a value, don't use reassignment
const input =
document.querySelector("input")
input.addEventListener
( "keyup"
, e =>
searchBooks(e.target.value) // gets filtered books
.then(convert) // passes books to convert, gets result
.then(console.log, console.error) // then log the result, or error
)
<input placeholder="search here..."/>
These are all common mistakes beginners make when first using Promises. I think this related Q&A will be very helpful to you.
In this example, we wrap a Promise around filter but it is purely synchronous and actually unnecessary -
const searchBooks = (query = "") =>
books.filter(b =>
b.name.includes(query)
)
In a real scenario, maybe the data is coming from over the network. Then we'd have use for Promises -
const fetchJson = (url = "") =>
fetch(url).then(res => res.json())
const searchBooks = (query = "") =>
fetchJson("./path/to/books.json") // fetch the books data
.then(books => // then using the books
books.filter(b => // filter the books
b.name.includes(query)
)
) // .then returns another promise
Related, async/await make it easy to write asynchronous code that appears more like synchronous code. Using async our function will automatically return a Promise, and it allows us to await any expression inside the function's body -
const fetchJson = (url = "") =>
fetch(url).then(res => res.json())
const searchBooks = async (query = "") => // <-- async keyword
{ const books =
await fetchJson("./path/to/books.json") // <-- await keyword
return books.filter(b =>
b.name.includes(query)
)
}

Microsoft Graph API Javascript SDK Promises

I just got rid of the promise chaining as it was very confusing and went ahead with async await. I'm still not able to get the required results. How do I get the result of finalResult() from the below code. It keeps returning promise pending. I tried doing the following
let sampleData = await finalResult()
Here how do I get the data from sampleData? I also tried attaching a then call to finalResult which didn't work either. Any help pls? All I need is the channelData in the code below
app.get("/graph/getChannelEvents", (req, res) => {
var idToken = req.query.idToken;
var teamId = req.query.teamId;
var channelData = req.query.channelData;
var tenantId = process.env.TENANT_ID;
if (!idToken) {
res.status(500).send("Could not find id token");
return;
}
request(
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
form: {
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
client_id: process.env.APP_ID,
client_secret: process.env.APP_Secret,
scope: "Calendars.Read",
requested_token_use: "on_behalf_of",
assertion: idToken,
},
},
async (error, response, body) => {
const accessToken = JSON.parse(body)["access_token"];
var client = MicrosoftGraph.Client.init({
defaultVersion: "v1.0",
debugLogging: true,
authProvider: (done) => {
done(null, accessToken);
},
});
async function finalResult() {
let cdata = await client
.api(`/groups/${teamId}/events`)
.header("Prefer", 'outlook.timezone="Pacific Standard Time"')
.select("subject,onlinemeeting,start,end")
.get();
let channelData = await cdata.value.map(async (org) => {
var channelId = url
.parse(decodeURIComponent(org.onlineMeeting.joinUrl))
.pathname.split("/")[3];
var sessionData = await client
.api(`/teams/${teamId}/channels`)
.filter(`startswith(id, '${channelId}')`)
.select("displayName")
.get();
let myData = await sessionData.value.map(async (u) => {
return {
sessionName: org.subject,
channelName: u.displayName,
channelId: channelId,
startDate: org.start.dateTime.split("T")[0],
endDate: org.end.dateTime.split("T")[0],
startTime: org.start.dateTime.split("T")[1],
endTime: org.end.dateTime.split("T")[1],
};
});
});
console.log(channelData);
}
finalResult();
}
);
});
A good solution for this would be to make it asynchronously with a promise wrapping the whole shown code and returning that promise with a function.
After that you would want to make an array of promises to be able to call them all at once and wait for all the results before continuing. you use Promise.all() to call all the promises in the array and use it as you would use a single promise with a .then(). The received value from that then() would be an array containing all of the results from the promiseArray's promises.
var getStuff = () => {
//First and big promise starts here
return new Promise( (resolve, reject) => {
client.api(`/groups/${teamId}/events`)
.header("Prefer", 'outlook.timezone="Pacific Standard Time"')
.select("subject,onlinemeeting,start,end")
.get()
.then((result) => {
// Create an array for your promises
var promisesArray = [];
result.value.map((org) => {
// Fill your array with every promise you need, each one containing just one call to your api
promisesArray.push(new Promise((arrayResolve, arrayReject) => {
console.log("top: " + org.subject);
var channelId = url
.parse(decodeURIComponent(org.onlineMeeting.joinUrl))
.pathname.split("/")[3];
client
.api(`/teams/${teamId}/channels`)
.filter(`startswith(id, '${channelId}')`)
.select("displayName")
.get()
.then((result) => {
const cdata = result.value.map((u) => {
console.log("down " + org.subject);
return {
sessionName: org.subject,
channelName: u.displayName,
channelId: channelId,
startDate: org.start.dateTime.split("T")[0],
endDate: org.end.dateTime.split("T")[0],
startTime: org.start.dateTime.split("T")[1],
endTime: org.end.dateTime.split("T")[1],
};
});
})
.then((result) => {
// Resolve individual promises
arrayResolve(result);
}).catch((err) => {
reject(err);
})
}));
})
// This executes all the promises at the same time
Promise.all(promisesArray).then( (finalResult) => {
// Resolve big and initial promise
resolve(finalResult);
}).catch((err) => {
reject(err);
})
})
})
}
(NOTE: I just wrapped your existing code in a function and completed the braces and parenthesis needed. Then I added the promises that would make this work as intended but some code needs to be added to make it work, as i noticed you called some variables that weren't defined inside your shown code)
After this, you could just call this function and use a '.then()' to receive the value you want.
Hope this helps!

How to map over data that `.find()` returns?

I have a simple function:
const oldUsers = Users.find(
{
isReminded: false,
creationDate: {
"$lt": new Date(Date.now() - thirtyDays),
},
},
);
and then:
export const notifyOldUsers = () =>
Array.isArray(oldUsers) ? oldUsers.map(async(user, i) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }});
await transporter.sendMail(sendMail));
}) : [];
};
But the problem is that oldUsers returns object, and if I console.log it, its a complicated Query object.
Question: How to to properly loop over the data, which .find() produces?
First, the Users.find() is an async operation, so oldUsers is always going to be undefined. You need to await it (if you weren't using promises you'd have to use a callback).
const oldUsers = await Users.find(...)
Second, your notifyOldUsers function is not async, so it runs the mapping function, but exits immediately, not waiting for the mapped async operations to finish.
Typically, when mapping async operations, you should use a Promise.all() to collect all the promises returned by the callback function and wait for them all to resolve.
export const notifyOldUsers = async () => {
const promises = oldUsers.map(async (user) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }})
await transporter.sendMail(sendMail))
})
return Promise.all(promises)
}
I've intentionally split up the mapping from the Promise.all() to illustrate the map of promises being returned. This can be further optimized to the following.
export const async notifyOldUsers = async () => {
return Promise.all( oldUsers.map(async (user) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }})
await transporter.sendMail(sendMail))
}) )
}
In both cases, this async function will return a new promise who's value will be an array containing the results of the entire map.

Get Knex.js transactions working with ES7 async/await

I'm trying to couple ES7's async/await with knex.js transactions.
Although I can easily play around with non-transactional code, I'm struggling to get transactions working properly using the aforementioned async/await structure.
I'm using this module to simulate async/await
Here's what I currently have:
Non-transactional version:
works fine but is not transactional
app.js
// assume `db` is a knex instance
app.post("/user", async((req, res) => {
const data = {
idUser: 1,
name: "FooBar"
}
try {
const result = await(user.insert(db, data));
res.json(result);
} catch (err) {
res.status(500).json(err);
}
}));
user.js
insert: async (function(db, data) {
// there's no need for this extra call but I'm including it
// to see example of deeper call stacks if this is answered
const idUser = await(this.insertData(db, data));
return {
idUser: idUser
}
}),
insertData: async(function(db, data) {
// if any of the following 2 fails I should be rolling back
const id = await(this.setId(db, idCustomer, data));
const idCustomer = await(this.setData(db, id, data));
return {
idCustomer: idCustomer
}
}),
// DB Functions (wrapped in Promises)
setId: function(db, data) {
return new Promise(function (resolve, reject) {
db.insert(data)
.into("ids")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
},
setData: function(db, id, data) {
data.id = id;
return new Promise(function (resolve, reject) {
db.insert(data)
.into("customers")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
}
Attempt to make it transactional
user.js
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(knex.transaction());
const idCustomer = await(user.insertData(trx, data));
return {
idCustomer: idCustomer
}
}),
it seems that await(knex.transaction()) returns this error:
[TypeError: container is not a function]
I couldn't find a solid answer for this anywhere (with rollbacks and commits) so here's my solution.
First you need to "Promisify" the knex.transaction function. There are libraries for this, but for a quick example I did this:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
This example creates a blog post and a comment, and rolls back both if there's an error with either.
const trx = await promisify(db.transaction);
try {
const postId = await trx('blog_posts')
.insert({ title, body })
.returning('id'); // returns an array of ids
const commentId = await trx('comments')
.insert({ post_id: postId[0], message })
.returning('id');
await trx.commit();
} catch (e) {
await trx.rollback();
}
Here is a way to write transactions in async / await.
It is working fine for MySQL.
const trx = await db.transaction();
try {
const catIds = await trx('catalogues').insert({name: 'Old Books'});
const bookIds = await trx('books').insert({catId: catIds[0], title: 'Canterbury Tales' });
await trx.commit();
} catch (error) {
await trx.rollback(error);
}
Async/await is based around promises, so it looks like you'd just need to wrap all the knex methods to return "promise compatible" objects.
Here is a description on how you can convert arbitrary functions to work with promises, so they can work with async/await:
Trying to understand how promisification works with BlueBird
Essentially you want to do this:
var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
This is because "async/await requires the either a function with a single callback argument, or a promise", whereas knex.transaction looks like this:
function transaction(container, config) {
return client.transaction(container, config);
}
Alternatively, you can create a new async function and use it like this:
async function transaction() {
return new Promise(function(resolve, reject){
knex.transaction(function(error, result){
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(transaction());
const idCustomer = await(person.insertData(trx, authUser, data));
return {
idCustomer: idCustomer
}
})
This may be useful too: Knex Transaction with Promises
(Also note, I'm not familiar with knex's API, so not sure what the params are passed to knex.transaction, the above ones are just for example).
For those who come in 2019.
After I updated Knex to version 0.16.5. sf77's answer doesn't work anymore due to the change in Knex's transaction function:
transaction(container, config) {
const trx = this.client.transaction(container, config);
trx.userParams = this.userParams;
return trx;
}
Solution
Keep sf77's promisify function:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
Update trx
from
const trx = await promisify(db.transaction);
to
const trx = await promisify(db.transaction.bind(db));
I think I have found a more elegant solution to the problem.
Borrowing from the knex Transaction docs, I will contrast their promise-style with the async/await-style that worked for me.
Promise Style
var Promise = require('bluebird');
// Using trx as a transaction object:
knex.transaction(function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx)
.then(function(ids) {
return Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
.then(trx.commit)
.catch(trx.rollback);
})
.then(function(inserts) {
console.log(inserts.length + ' new books saved.');
})
.catch(function(error) {
// If we get here, that means that neither the 'Old Books' catalogues insert,
// nor any of the books inserts will have taken place.
console.error(error);
});
async/await style
var Promise = require('bluebird'); // import Promise.map()
// assuming knex.transaction() is being called within an async function
const inserts = await knex.transaction(async function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
const ids = await knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx);
const inserts = await Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
await trx.commit(inserts); // whatever gets passed to trx.commit() is what the knex.transaction() promise resolves to.
})
The docs state:
Throwing an error directly from the transaction handler function automatically rolls back the transaction, same as returning a rejected promise.
It seems that the transaction callback function is expected to return either nothing or a Promise. Declaring the callback as an async function means that it returns a Promise.
One advantage of this style is that you don't have to call the rollback manually. Returning a rejected Promise will trigger the rollback automatically.
Make sure to pass any results you want to use elsewhere to the final trx.commit() call.
I have tested this pattern in my own work and it works as expected.
Adding to sf77's excellent answer, I implemented this pattern in TypeScript for adding a new user where you need to do the following in 1 transaction:
creating a user record in the USER table
creating a login record in the LOGIN table
public async addUser(user: User, hash: string): Promise<User> {
//transform knex transaction such that can be used with async-await
const promisify = (fn: any) => new Promise((resolve, reject) => fn(resolve));
const trx: knex.Transaction = <knex.Transaction> await promisify(db.transaction);
try {
let users: User [] = await trx
.insert({
name: user.name,
email: user.email,
joined: new Date()})
.into(config.DB_TABLE_USER)
.returning("*")
await trx
.insert({
email: user.email,
hash
}).into(config.DB_TABLE_LOGIN)
.returning("email")
await trx.commit();
return Promise.resolve(users[0]);
}
catch(error) {
await trx.rollback;
return Promise.reject("Error adding user: " + error)
}
}

Categories