I'm fairly new to JavaScript, and I'm finding the concept of promises and "then" statements really hard to pin down. I'm working on a website using Firebase, and I'm trying to get user data from the database. Here's what I have at the moment:
// Function that fetches a user's data from the database.
getUserData = function(uid) {
var database = firebase.firestore();
var docRef = database.collection('users').doc(uid);
var userData = docRef.get().then(function(doc) {
if (doc.exists) {
return doc.data();
} else {
console.log("User does not exist.");
return null;
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
return userData;
}
Currently, this function returns a Promise object. When I log the object to the console, it says that the promise status is resolved and its value is the value of doc.data(). What I really want is to pass the value of doc.data() back up the "chain" to the getUserData function so that I can return and use it elsewhere. Is there a simple way to do this? I've tried to find a way to get the "value" of a Promise out of the object itself, but I've found nothing, and so far I have failed to find any other way to do what I want.
I'm sure it's a lot simpler than it seems, and I feel like an idiot for asking this sort of thing, but I've been searching for a couple of hours and none of the explanations I've found are helping. I would greatly appreciate any advice. Thanks!
Edit: Solved! Thanks for the help, everyone! Just in case this helps someone in the future who stumbles upon it, I'm leaving my final (working) code here. Unfortunately there's no way to call it from a synchronous function, but it ended up being good enough for my purposes:
// Function that fetches a user's data from the database.
async function getUserData(uid) {
var database = firebase.firestore();
var docRef = database.collection('users').doc(uid);
var doc = await docRef.get();
if (doc.exists) {
return doc.data();
} else {
console.log("User does not exist.");
return null;
}
}
First you must understand that Promise are mainly used for asynchronous operation, and they do not store the value UNTIL the asynchronous operation is resolved.
Promises to not expose any "get" method to retrieve directly their value but only a then method to execute a callback once the promise is resolved. Thus, with Promise you always need to "wait" for it to have resolved to read the value.
Now, you have two choices :
The first is to use the old way of then() and catch().
You make your function return a Promise (like you did) and where you call your function, you can use then to execute a callback with your result.
getUserData("anId").then((userData) => console.log('the async result is' + userData))
You can also use catch to handle the error :
getUserData("anId")
.then((userData) => console.log('the async result is' + userData))
.catch((error) => console.error('wooopsie : ' + error))
As for the "new" way of doing things, you can use the async and await keywords of JS to handle your Promises.
Basically, a function declared async will always return a Promise and will define a context allowing to use the await keyword. And await will allow you to write your Promise handling just like synchronous code.
async function(){
const userData = await getUserData("anId");
console.log('the async result is' + userData);
Finally, if you need error handling, you will need to wrap the await instruction with a try-catch.
Hope this helps.
The reason for using promises is that they make asynchronous code easier to work with. Unfortunately the code can still be a little tricky if you're not used to it. You're probably better off using async/await in most situations as it will be easier to work with and also probably easier to understand:
async function getUserData(uid) {
var database = firebase.firestore();
var docRef = database.collection('users').doc(uid);
var doc = await docRef.get();
if(doc.exists)
return doc.data();
return null;
}
This is how you would use it:
// code to execute before
getUserData(uid).then(userData => {
if(userData) {
// code to execute after
}
else console.log("User does not exist.");
}).catch(error => {
console.log("Error getting document:", error);
});
Alternatively (Async/Await):
async function doSomething(uid) {
// code to execute before
const userData = await getUserData(uid);
// code to execute after
}
Async/Await with custom error handling:
async function doSomething(uid) {
// code to execute before
try {
const userData = await getUserData(uid);
if(userData) {
// code to execute after
}
else console.log("User does not exist.");
}
catch(error) {
console.log("Error:", error);
}
}
To get value from promise you need to use .then, so in your case to get data in the line you call getUserData, you need to use same construct (getUserData.then((data) => {}))
Related
I'm having trouble understanding how to properly end a firestore trigger. From what I read from this and this, it seems you should only return null to end a function if there's no async code such as to quickly end if a condition isn't met. When I return null in my scenario below, it seems to work fine. Is there a better practice for what I'm doing that I'm missing?
I need to log my own custom error message which why I need the catch. I know I could return Promise.all here instead of null in the try block, but this is just sudo code for my scenario.
export const delacc = functions.auth.user().onDelete(async (user) => {
const userUID = user.uid;
try{
await admin.firestore().collection("users").doc(userUID).delete();
await admin.firestore().collection("spam").doc(userUID).delete();
await admin.firestore().collection("photos").doc(userUID).delete();
return null;
}catch(error){
functions.logger.error(error)
return null
}
});
There's no hard requirement to return null. In fact, async functions always return a promise, no matter what you do inside the function. The promise it returns is based on the completion of any other promises that you await during processing. Even if you explicitly return null, the function is still actually just returning a promise that is immediately fulfilled with the value null. It has no effect on the final outcome, since Cloud Functions onDelete triggers don't use the fulfilled value in any way. The important thing is that the function indeed returns a promise that indicates when all the work is complete (and, as I stated, async functions always do that if you use await correctly inside the function on all async work).
When you're not using async/await, I advise programmers to always return a promise, or null if there is no async work. The null there is an explicit way to tell the reader of your code that you do not intend for Cloud Functions to wait for any async work before fully terminating the function. It helps also helps satisfy eslint or TypeScript warnings, which will suggest to you that you should return something. The value itself isn't really important - it's what you're communicating to others about the termination of your function. Code readability is important if you work with others.
What I'd do is make the delete operations atomic—delete all of the documents or none of them—and return the promise returned by the batch since there isn't any other task performed in this function (which makes me think returning null isn't a necessary abstraction). If the batch throws an error, throw the client a new HTTPS error (along with the batch error) which (a) automatically terminates the cloud function and (b) gives the client the batch error to evaluate, which, depending on the reason, could warrant a retry or an error message to the end user.
export const delacc = functions.auth.user().onDelete(async (user) => {
const userUID = user.uid;
const db = admin.firestore();
const batch = db.batch();
try {
batch.delete(db.collection("users").doc(userUID));
batch.delete(db.collection("spam").doc(userUID));
batch.delete(db.collection("photos").doc(userUID));
const writeResult = await batch.commit();
return writeResult;
} catch(error) {
functions.logger.error(error);
throw new functions.https.HttpsError("unknown", "batch-error", error);
}
});
I'm a Node.js student, and I'm writing a brief example. The get on /org works as expected.
With the get on /orgg I tried to segregate the server "fetch the data" logic from the controller. However, the getRowsAsync() immediately returns a Promise.
This "put the await in express.get()" isn't good for hiding logic. If my biz logic needed some sequential Promises then the biz logic would have to bubble into the controller.
How can I do the equivalent to calling "doBizLogic()" in the controller, hiding my awaits, and having the controller wait for the logic to complete? Must I pass a callback function to the biz logic to make this scheme work?
Here is my index.js. I omit database.js, but I borrowed it from https://mhagemann.medium.com/create-a-mysql-database-middleware-with-node-js-8-and-async-await-6984a09d49f4
const express = require("express");
const urlPort = 3000;
let app = express();
let mysql = require("mysql2");
let pool = require("./database");
app.listen(urlPort, () => {
console.log("Server is running at port " + urlPort);
});
app.get("/", (req, res) => {
res.send("This app responds to /org and /org/:id only.");
});
app.get("/org", async (req, res) => {
let rows = await getRows("select * from org");
// the log always prints a JSON array.
console.log("in app.get for /org, rows: ", rows);
res.send(rows);
});
app.get("/orgg", (req, res) => {
let rows = getRowsAsync();
// the log() prints immediately prints a Promise.
console.log("in app.get for /orgg, rows: ", rows);
res.send(rows);
});
function getRows(sql, params = []) {
let rows = pool.query(sql, params);
// the log() prints a Promise.
console.log("in getRows, rows: ", rows); // returns
return rows;
}
async function getRowsAsync() {
let rows = await getRows("select * from org");
// the log() prints a JSON array, once it is finally called.
console.log("in getRowsAsync, rows: ", rows);
return rows;
}
Your specific questions
How can I do the equivalent to calling "doBizLogic()" in the controller, hiding my awaits, and having the controller wait for the logic to complete?
You can't hide that logic. Any API that "might" be asynchronous must be designed as an asynchronous API and the caller needs to know how to use the asynchronous API properly.
You cannot hide asynchronous-ness because there's no way in Javascript to make an asynchronous operation into a synchronous one. If any part of your operation is asynchronous, then it will need an asynchronous interface to communicate the result and completion (via a promise, a callback or an event).
Must I pass a callback function to the biz logic to make this scheme work?
You could use a callback, but I would suggest just returning a promise that the caller can use await on as that's a more modern design than a callback and is somewhat easier to program with, particularly if there are other asynchronous operations in the same function. The caller will have to make use of that returned promise for things to work properly.
So, just have your caller use await. The caller has to "know" it's an asynchronous interface anyway because you program with asynchronous interfaces differently than synchronous interfaces.
This "put the await in express.get()" isn't good for hiding logic. If my biz logic needed some sequential Promises then the biz logic would have to bubble into the controller.
Yes, that's the way it is in Javascript. A caller has to know if they are calling a synchronous or asynchronous API so they know how to get the result/err and completion. There is no way around that unless you design all interfaces as asynchronous and treat all calls to those APIs as asynchronous (which is not efficient for entirely synchronous APIs).
Why is await not being honored in a sub-function?
In this piece of code:
function getRows(sql, params = []) {
let rows = pool.query(sql, params);
// the log() prints a Promise.
console.log("in getRows, rows: ", rows); // returns
return rows;
}
The code is not properly handling the asynchronous pool.query(). That function apparently returns a promise and you aren't waiting for that promise to resolve either with await or .then(). Therefore, when you try to log the value, you just get the promise. This function will be returning the promise so a caller can get the value by using .then() or await on that promise.
Await requires proper promise implementation
I will also mention that in order for await to work properly for your callers, your function must return a promise that resolves or rejects when the asynchronous operation is complete and any resolved value or rejected error is in that promise also. So, just slapping an await on a function that isn't returning a promise that is properly wired into the asynchronous operations will not work. The promise must be correctly implemented for await to do its job.
Use a promise-supporting version of mysql
Since it appears you're using mysql, I'd strong suggest you get the module that implements a promise interface for the database so you can do all your asynchronous coding with promises, not plain callbacks.
The link you had in your question to a 2018 article shows lots of mysql examples using plain callbacks. I would not recommend starting a new project using plain callbacks for asynchronous operations. You will quickly learn to appreciate the advantages of using promise-based interfaces once you start doing that and promises are the modern way of handling asynchronous operations in Javascript.
You must catch all promise rejections
P.S. Any promise you use MUST have a .catch() or an await with a try/catch somewhere in the call chain that will catch a potential rejected promise. So, all your express request handlers that are calling your asynchronous APIs are missing that proper error handling. Without that, you will get an uncaught promise rejection. At best, the request handler will just never send a response. At worst, your server will shut-down. All possible rejected promises must be caught somewhere in your code.
So, something like this:
app.get("/org", async (req, res) => {
let rows = await getRows("select * from org");
// the log always prints a JSON array.
console.log("in app.get for /org, rows: ", rows);
res.send(rows);
});
should be something like this:
app.get("/org", async (req, res) => {
try {
let rows = await getRows("select * from org");
// the log always prints a JSON array.
console.log("in app.get for /org, rows: ", rows);
res.send(rows);
} catch(e) {
console.log(e);
res.sendStatus(500);
}
});
Or, if you want to handle all errors centrally:
app.get("/org", async (req, res) => {
try {
let rows = await getRows("select * from org");
// the log always prints a JSON array.
console.log("in app.get for /org, rows: ", rows);
res.send(rows);
} catch(e) {
console.log(e);
next(e); // forward to central Express error handler
}
});
If you want to catch the error in your business logic so you can do something with it, but still eventually return an error back to your controller functions, then you can catch and rethrow:
async function getRows(params) {
try {
let rows = await pool.query("select * from org", params);
console.log("in getRows, rows: ", rows);
return rows;
} catch(e) {
// catch the error, log it and do whatever else you want with it
// either return a value or rethrow some error
// usually, you would throw some error here
// so a failed operation rejects the promise
// this function returns
console.log(e);
// you can throw a different error if you want to invent
// your own specific errors, rather than pass back db errors
throw e;
}
}
I have a method like this:
doSomeRequests(listOfRequestParameters) {
let requests = listOfRequestParameters.map(parmeter => {
return axios.get(url + parmeter)
.then(data => {
const parameters= data.parameters;
return axios.delete(url, parameters)
})
})
return Promise.all(requests);
}
In this function I want to complete API requests for each element in a list (listOfRequestParameters), but for every element in the list I have to do two API requests.
The axios methods both return Promises.
The problem is that I do catch the result of the Promise returned y doSomeRequests method, but if one API requests fail I get an UnhandledPromiseRejectionWarning.
What am I missing? Is there a way to do this kind of Promise chain?
Thanks to #charlietfl for asking me if I really do doSomeRequests(..).then().catch(). Turns out I don't, I accidentally did doSomeRequests(..).then().then() but was somehow convinced the I need to search for the error in the "complex" Promise chain.
Anyway, I am sorry for the useless question, thank you for your help.
You need to make sure the map call returns a list of promises, simplifying with async/await you'll get something along the lines:
async doSomeRequests(listOfRequestParameters) {
return Promise.all(listOfRequestParameters.map(async parmeter => {
const data = await axios.get(url + parameter);
const parameters = data.parameters;
return axios.delete(url, parameters);
}));
}
await doSomeRequests(listOfRequestParameters);
I understand that this is a basic question, but I can't figure it out myself, how to export my variable "X" (which is actually a JSON object) out of "for" cycle. I have tried a various ways, but in my case function return not the JSON.object itself, but a "promise.pending".
I guess that someone more expirienced with this will help me out. My code:
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
getData(url).then(function(result) { //promise.pending w/o .then
let x = result; //here is real JSON that I want to export
});
}
console.log(x); // -element is not exported :(
Here's some cleaner ES6 code you may wish to try:
const fetch = require("node-fetch");
Promise.all(
server.map((srv) => {
const url = String(srv.name);
return fetch(url)
.then((response) => response.json())
.catch((err) => console.log(err));
})
)
.then((results) => {
console.log(results);
})
.catch((err) => {
console.log('total failure!');
console.log(err);
});
How does it work?
Using Array.map, it transforms the list of servers into a list of promises which are executed in parallel. Each promise does two things:
fetch the URL
extract JSON response
If either step fails, that one promise rejects, which will then cause the whole series to reject immediately.
Why do I think this is better than the accepted answer? In a word, it's cleaner. It doesn't mix explicit promises with async/await, which can make asynchronous logic muddier than necessary. It doesn't import the fetch library on every loop iteration. It converts the server URL to a string explicitly, rather than relying on implicit coercion. It doesn't create unnecessary variables, and it avoids the needless for loop.
Whether you accept it or not, I offer it up as another view on the same problem, solved in what I think is a maximally elegant and clear way.
Why is this so hard? Why is async work so counterintuitive?
Doing async work requires being comfortable with something known as "continuation passing style." An asynchronous task is, by definition, non-blocking -- program execution does not wait for the task to complete before moving to the next statement. But we often do async work because subsequent statements require data that is not yet available. Thus, we have the callback function, then the Promise, and now async/await. The first two solve the problem with a mechanism that allows you to provide "packages" of work to do once an asynchronous task is complete -- "continuations," where execution will resume once some condition obtains. There is absolutely no difference between a boring node-style callback function and the .then of a Promise: both accept functions, and both will execute those functions at specific times and with specific data. The key job of the callback function is to act as a receptacle for data about the asynchronous task.
This pattern complicates not only basic variable scoping, which was your main concern, but also the issue of how best to express complicated workflows, which are often a mix of blocking and non-blocking statements. If doing async work requires providing lots of "continuations" in the form of functions, then we know that doing this work will be a constant battle against the proliferation of a million little functions, a million things needing names that must be unique and clear. This is a problem that cannot be solved with a library. It requires adapting one's style to the changed terrain.
The less your feet touch the ground, the better. :)
Javascript builds on the concept of promises. When you ask getData to to do its work, what is says is that, "OK, this is going to take some time, but I promise that I'll let you know after the work is done. So please have faith on my promise, I'll let you know once the work is complete", and it immediately gives you a promise to you.
That's what you see as promise.pending. It's pending because it is not completed yet. Now you should register a certain task (or function) with that promise for getData to call when he completes the work.
function doSomething(){
var promiseArray = [];
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
promiseArray.push(getData(url)); // keeping track of all promises
}
return Promise.all(promiseArray); //see, I'm not registering anything to promise, I'm passing it to the consumer
}
function successCallback(result) {
console.log("It succeeded with " + result);
}
function failureCallback(error) {
console.log("It failed with " + error);
}
let promise = doSomething(); // do something is the function that does all the logic in that for loop and getData
promise.then(successCallback, failureCallback);
I use dom-to-image.js for converting dom to png image. As dom-to-image.js uses promise, the code executes asynchronously. I want to execute .then function synchronously.
I have the following code:
domtoimage.toPng(document.getElementById("main")).then(function(dataUrl) {
console.log(dataUrl);
}).catch(function(error) {
console.error('oops, something went wrong!', error);
});
console.log("this console should be executed after console.log(dataUrl)")
I want to execute .then function first, before executing console.log("this console should be executed after console.log(dataUrl)").
Please tell me some way to achieve this.
There are of course legit reasons to force synchronous execution of a promise in js. The most obvious is when require'ing a file that needs to initialize using some asynchronous stuff, and optimization is not an overriding concern.
Seems like the package synchronized-promise seems like it ought to do the trick. In your case (warning - untested..):
const dti = () => docstoimage.toPng(document.getElementById("main"))
.then(dataUrl => console.log('url: ', dataUrl))
.catch(err => console.error('oops: ', err))
const sp = require('synchronized-promise')
const syncDti = sp(dti)
syncDti() // performs the 'synchronized version'
console.log("this console should be executed afterwards")
You didn't mention if it's done in browser or in NodeJs. For NodeJs that's possible by using execSync or spawnSync from child_process:
import { spawnSync } from "child_process";
function doSyncStuff(domObj){
const output = spawnSync(...);
console.log("this logs in server");
return output;
bit of a necro, but I'm the author of https://www.npmjs.com/package/synchronous-promise - which was around before this question was raised and does what you want (:
Async functions are a promise.
You have to use the promise syntax with them or use await (async-await syntax) on them in another async function.
An example of the regular promise syntax would be: myPromise.then(() => {})
For what you are looking for, you can use the promise syntax on them to wait for them as if they aren't a promise. Do take care though, as the code in myPromise.then(() => {}) is synchronous and can't use async-await features. I've wrapped things in a main function but you don't need to for extra code but you don't need to.
You can then run the main function through the promise method.
For e.g:
async function myFunction() {
// Do stuff
}
async function main() {
// Do stuff
await myFunction()
}
async function mySecondaryFunction() {
// Do stuff
}
function mySynchronousFunction() {
console.log("hello")
}
main().then(() => {
mySynchronousFunction()
mySecondaryFunction().then(() => {
console.log("idk")
}
})
See, we can use both types of functions!
Do know, though that this doesn't actually make promises synchronous.
It just acts like they're synchronous.
EDIT: Uhhh, I just realized you could put the console.log you want to execute after in both the try and catch functions.
For those stumbling upon this now:
If you're not concerned with IE support, you could use async and await to execute the promise as though it were synchronous. The await syntax will pause execution of the function until the promise resolves, letting you write the code as if there wasn't a promise. To catch an error, you wrap it in a try/catch block.
The only catch is that using the await syntax requires you to wrap it inside a function declared with async.
async function toPng() {
try {
let dataUrl = await domtoimage.toPng(document.getElementById("main"));
console.log(dataUrl);
}
catch (error ) {
console.error('oops, something went wrong!', error);
}
console.log("this console should be executed after console.log(dataUrl)")
}