google actions webhook promise await - what am I doing wrong? - promise

I am struggling with getting JS to wait for a process to finish before continuing
With a simple wait, the process continues while it is meant to wait
const functions = require('firebase-functions');
const app = conversation();
// wait ms milliseconds
function wait(ms) {
console.log("in the wait");
return new Promise(r => setTimeout(r, ms));
}
async function getData() {
console.log(`start wait `);
await wait(5000);
console.log(`end wait `);
}
app.handle('findit', conv => {
console.log(`-----> handle activated `);
console.log(`-----> BEFORE call getdata`);
getData();
console.log(`--------END OF HANDLE ----------------------`);
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);```
[Timing logs][1]
[1]: https://i.stack.imgur.com/96Ydv.png

const app = conversation();
// wait ms milliseconds
function wait(ms) {
console.log("in the wait");
return new Promise(r => setTimeout(r, ms));
}
async function getData() {
console.log(`start wait `);
await wait(5000);
console.log(`end wait `);
}
app.handle('findit', async conv => {
console.log(`-----> handle activated `);
console.log(`-----> BEFORE call getdata`);
getData();
console.log(`--------END OF HANDLE ----------------------`);
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);```
the issue was the conv was not async - the addition of the async to the app.handle('findit', async conv => { fixed the issue.

Related

Async await not working for function - create pdf document and then email it

const pdf = require('pdfkit')
const QR = require('qrcode')
const emailTickets = async (userEvent, tickets) => {
await createQRCodes(tickets)
await createTicketPDF(tickets, userEvent)
await sendGridEmail(emailDetails, true)
}
The problem seems to be that async/await isn't working properly for the createTicketPDF function.
When I run the above emailTickets function a blank pdf document is emailed. However, when I run it with the below setTimeOut, the pdf contains all the detail that I want.
const emailTickets = async (userEvent, tickets) => {
await createQRCodes(tickets)
await createTicketPDF(tickets, userEvent)
setTimeout(async() => {await sendGridEmail(emailDetails, true)}, 5000);
}
I would prefer a solution where the code waited for the createTicketPDF function to finish before calling the sendGridEmail function.
Below is the code for the createTicketPDF function:
const createTicketPDF = async (tickets, userEvent) => {
const doc = new pdf
doc.pipe(fs.createWriteStream(`./tickets/${tickets[0]._id}.pdf`))
await tickets.map(async(ticket, index)=> {
return(
await doc.fontSize(30),
await doc.text(userEvent.title),
await doc.fontSize(10),
await doc.moveDown(),
await doc.text(`Venue: ${userEvent.venue}, ${userEvent.address1} `),
await doc.moveDown(),
await doc.fontSize(20),
await doc.text(`Ticket ${index+1} of ${tickets.length}: ${ticket.ticketType}`),
await doc.image(`./qrCodes/${ticket._id}.png`, {
align: 'center',
valign: 'center'
}),
await doc.addPage()
)
})
doc.end()
}
I had a forEach loop in this function but replaced it with a map on learning that forEach loops don't work well with async await.
I have also tried using (const ticket of tickets){} but this didn't work either
VS code is suggesting that none of the awaits within the map do anything
For completeness, below is the createQRCodes. This function is working perfectly.
const createQRCodes = async (tickets) => {
let CreateQRCodes = tickets.map(ticket => {
return(
QR.toFile(`./qrCodes/${ticket._id}.png`, String([ticket._id, ticket.randomNumber, ticket.creationTime.getTime(), ticket.userEvent]))
)})
await Promise.all(CreateQRCodes)
}
Any ideas where I am going wrong
Twilio SendGrid developer evangelist here.
As far as I can tell from the documentation, PDFKit is a synchronous library. The only thing that is asynchronous is writing the PDF to disk. So your code should not need to await anything within the createTicketPDF function. You do need to listen for the stream to finish though, so you could return a promise that resolves when the stream is finished, like this:
const createTicketPDF = (tickets, userEvent) => {
const doc = new pdf
const stream = doc.pipe(fs.createWriteStream(`./tickets/${tickets[0]._id}.pdf`))
tickets.map((ticket, index)=> {
return(
doc.fontSize(30),
doc.text(userEvent.title),
doc.fontSize(10),
doc.moveDown(),
doc.text(`Venue: ${userEvent.venue}, ${userEvent.address1} `),
doc.moveDown(),
doc.fontSize(20),
doc.text(`Ticket ${index+1} of ${tickets.length}: ${ticket.ticketType}`),
doc.image(`./qrCodes/${ticket._id}.png`, {
align: 'center',
valign: 'center'
}),
doc.addPage()
)
})
doc.end()
const promise = new Promise((resolve) => {
stream.on("finish", () => {
resolve();
})
});
return promise;
}
const emailTickets = async (userEvent, tickets) => {
await createQRCodes(tickets)
await createTicketPDF(tickets, userEvent)
await sendGridEmail(emailDetails, true)
}

rxjs call function periodically or when timer expires

I have a need to call a function every 5000ms after the function finishes.
how can i achieve this.
my current implementation just keeps calling every 5000ms even if function takes longer
const { BehaviorSubject, timer} = require("rxjs");
const subject = new BehaviorSubject(false)
async function doit() {
console.log('hey')
subject.next(true)
}
timer(0, 5000).subscribe(() => {
doit()
})
I'm not sure if this was your intent but my solution starts counting the five second interval only after the function has finished. See the Codepen.
import { BehaviorSubject, of } from 'https://cdn.skypack.dev/rxjs#7.x?min';
import { exhaustMap, delay, repeat } from 'https://cdn.skypack.dev/rxjs#7.x/operators?min';
const subject = new BehaviorSubject(false)
const startTime = Date.now();
async function doit() {
console.log('Time elapsed: '+(Date.now()-startTime));
await new Promise(resolve => setTimeout(resolve, 1200));
subject.next(true)
}
of(undefined).pipe(
exhaustMap(doit),
delay(5000),
repeat()
).subscribe();
The following seems to work too:
import { BehaviorSubject, defer } from 'https://cdn.skypack.dev/rxjs#7.x?min';
import { delay, repeat } from 'https://cdn.skypack.dev/rxjs#7.x/operators?min';
const subject = new BehaviorSubject(false)
const startTime = Date.now();
async function doit() {
console.log('Time elapsed: '+(Date.now()-startTime));
await new Promise(resolve => setTimeout(resolve, 1200));
subject.next(true)
}
defer(doit).pipe(
delay(1000),
repeat()
).subscribe();
Use exhaustMap, it will not take new item until it finishes doit. Make sure do it returns observable
timer(0, 5000).pipe(
exhaustMap(_ => doit())
).subscribe();

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

const accounts = await web3.eth.getAccounts();
App = {
load: async () => {
await App.loadWeb3(
await App.loadAccount()
)
},
loadWeb3: async () => {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(web3.currentProvider)
} else {
window.alert("Please connect to Metamask.")
}
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// Request account access if needed
await ethereum.enable()
// Acccounts now exposed
web3.eth.sendTransaction({/* ... */})
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// Acccounts always exposed
web3.eth.sendTransaction({/* ... */})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
}
},
loadAccount: async () => {
App.account = web3.eth.accounts[0]
console.log(App.account)
}
}
$(() => {
$(window).load(() => {
App.load()
})
})
The error is in LINE 1 where I get the accounts from Ganache but await is valid only for async.
What changes should I make in this code to remove the error? Please help me.
If I remove this line the error says that it cannot access accounts and after this await does not work.
Is there any way to make this piece of code in the form of an ASYNC function?
await calls can only be made in functions marked as async. As you have used await in line 1 it is not wrapped in an async function. You can wrap your code in a async function and then call that function. e.g something like:
const main = async () => { // <- the async wrapper function
const accounts = await web3.eth.getAccounts();
// .... rest of your code
$(() => {
$(window).load(() => {
App.load()
})
})
}
main()
Or if you want to be more advanced and not save the function at all
(async ()=>{
const accounts = await web3.eth.getAccounts();
// .... rest of your code
})() // <- call the function right after declaring it

Why does this little rxjs-code-snippet using the concat-operator throw an error?

Preconditions:
The ref.getDownload() returns an Observable which only can be subscribed, if the
task.snapshotChanges()-Observable completed.
This code-snippet works:
task.snapshotChanges().subscribe({
complete: () => {
ref.getDownloadURL().subscribe((downloadUrl) => console.log(downloadUrl));
}
});
This code-snippet does NOT work:
concat(
task.snapshotChanges(),
ref.getDownloadURL()
).pipe(
last()
).subscribe((downloadUrl) => console.log(downloadUrl));
getDownloadUrl throws an error (404 file not found), because it seems
ref.getDownloadUrl is subscribed to early.
Why subscribes the ref.getDownloaded()-Observable and does not wait until task.snapshotChanges() completes? The concat-operator should ensure this behaviour.
Or am I wrong?
The function ref.getDownloadURL() is called when the concat(..) Observable is created. See:
const { of, concat } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), fetch2()).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
ref.getDownloadURL() seems to query the database directly when it gets called and not when the Observable it returns gets subscribed to.
You can wrap ref.getDownloadURL() with defer to only execute it when the Observable is subscribed to.
const { of, concat, defer } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), defer(() => fetch2())).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
Also see my answer here https://stackoverflow.com/a/57671521/9423231

Cancellation on unsubscribe only if observable hasn't completed

I have an observable for which I would want to call cancellation (teardown) logic when subscriber unsubscribes from it but only if the source observable haven't completed yet (or failed) by itself.
The built-in finalize operator lets to register custom callback when unsubscribe occurs but it being called whenever the unsubscription was caused by subscriber or completion of the source observable.
I implemented the this helper function:
function withCancellation(source, onCancel) {
return new Observable(subscriber => {
let completed = false;
const cancellable = source.pipe(
tap({
error: () => { completed = true; },
complete: () => { completed = true; },
})
);
const subscription = cancellable.subscribe(subscriber);
subscription.add(() => { if (!completed) onCancel(); });
return subscription;
});
}
Which I can use the following way:
const sourceStream = startJob(jobId); // returns source observable
const cancellableStream = withCancellation(sourceStream, () => stopJob(jobId));
Is there any more concise way to achieve the same using any built-in primitives?

Resources