I using Cloud Functions for Firebase to build HTTP endpoint. Inside this endpoint I am trying to read some data from Firebase Realtime Database synchronously by this method:
function getSomethingFromDbSynchronously (currency) {
return new Promise((resolve, reject) => {
var db = admin.database();
var ref = db.ref("someref");
ref.orderByChild("somechild").equalTo("something").once("value", function (snapshot) {
resolve(snapshot);
});
});
}
But it is doesn't works for me. My API route returns by the first return statement before this request to the DB ends.
What am I do wrong ?
The code looks fine: you're creating a new promise and returning that from getSomethingFromDbSynchronously(). But the code that calls getSomethingFromDbSynchronously() will then need to wait for the promise to resolve, with something like:
getSomethingFromDbSynchronously("currency").then(function(snapshot) {
console.log(snapshot.val());
});
There is no way to make this synchronous, although you could look into the new async and await keywords, which simply make the above read as if it happens synchronously.
Note, that your code is a bit longer than needed. Since once() already returns a promise, you might as well return that directly:
function getSomethingFromDbSynchronously (currency) {
var db = admin.database();
var ref = db.ref("someref");
return ref.orderByChild("somechild").equalTo("something").once("value");
}
Related
I am trying to send data from my firebase database back to my client in a cloud function. I want to return the entire json child object. Here is the code for my cloud function:
exports.addNumbers = functions.https.onCall((data, context) => {
admin.database().ref('store/ingredients').once('value', function(snapshot) {
return snapshot.val();
});
});
Here is my client code that is invoking this cloud function and accessing its data:
const addNumbers = firebase.functions().httpsCallable('addNumbers');
addNumbers().then(result => {
console.log(result.data + "in client");
});
But the data returned in the client is null and the cloud function is returning only object [Object] in the firebase logs. Please someone help me.
You're almost there. You just need to return something from the top-level code in your Cloud Function.
The easiest way is to use a then clause:
exports.addNumbers = functions.https.onCall((data, context) => {
return admin.database().ref('store/ingredients').once('value').then((snapshot) => {
return snapshot.val();
});
});
Now the value from the database "bubbles up" to the calling code, and is then returned out of your function as a promise.
On modern JavaScript versions, you can make this code easier to read by using async/await:
exports.addNumbers = functions.https.onCall(async (data, context) => {
let snapshot = await admin.database().ref('store/ingredients').once('value')
return snapshot.val();
});
This works exactly the same under the hood (it's really just syntactic sugar), but most developers find it much easier to read.
I have a large number of images that I want to download. I'm using the request-promise package to download the images from their URL(s). Since there were a large number of images, the server was getting overloaded and the downloads would hang or get corrupted, so I decided to use the promise-limit library to set the concurrency limit. Here is my code :
const fs = require('fs');
const request = require('request-promise');
const promiseLimit = require('promise-limit');
var limit = promiseLimit(30);
async function download(data) {
console.log(data);
return Promise.all(data.map( (obj) => {
return limit(() => downloadRequest(obj))
})).then(() => {
console.log("All images downloaded.")
})
function downloadRequest(obj) {
var img = obj.dest;
var url = obj.url;
var req = request(url);
req.pipe(fs.createWriteStream(img));
}
I replicated the sample exactly as is given in the github page, but the method returns without ever having the Promise.all() fulfilled. I do not understand what I am doing wrong, I thought that Promise.all() will definitely wait till it resolves all promises.
In the backend, this call is used as
...
.then((data) => {
return downloader.download(data);
})
.then(() => {
var filename = "Sample.pdf";
// Pages.json is written early in the chain, contains URL and corresponding image paths
return generator.generate('./Pages.json', filename);
});
Does this mean NodeJS is already trying to generate the file out of pages.json? How can I make this part of the code synchronous to download?
Your function downloadRequest() does not return a promise. It must return a promise that is tied to the asynchronous operation in contains such that the promise is resolved when that asynchronous operation is complete or rejected when that asynchronous operation has an error. Only when it does that can the limit() package properly do its job.
Since you're using a stream and piping it in downloadRequest(), you will have to manually construct a promise and then monitor the various events in the stream to know when it's done or has an error so you can resolve or reject that promise.
Here's an idea how to make downloadRequest() properly return a promise:
function downloadRequest(obj) {
return new Promise((resolve, reject) => {
const img = obj.dest;
const url = obj.url;
const req = request(url);
req.on('error', reject);
const ws = fs.createWriteStream(img);
ws.on('error', reject);
req.pipe(ws).on('finish', resolve);
});
}
And, it is now recommended to use the pipeline() function instead of .pipe() because it does more complete cleanup in error conditions and there is also a promise version of that built-in:
const { pipeline } = require('stream/promises');
function downloadRequest(obj) {
return pipeline(request(obj.url), fs.createWriteStream(obj.dest));
}
P.S. In case you didn't know, the request() library has been deprecated and it is recommended that you not use it in new projects any more. There is a list of alternative libraries to choose from here, all of which also have built-in promise support. I've looked at the various choices, tried several and decided I'm using got() in my work.
We are unit testing a React-Native application (using Jest) which does various API calls using fetch.
We have mocked calls to fetch in our API call functions in order to test them. This works well so far. We also have functions that combine these API calls and operate some logic on them.
For example, here is one function that, given a token, will get the related user's first project (project[0]) and return the list of items from this project.
export async function getAllItems(token) {
try {
const response = await getCurrentUser(token); // fetch called inside
const responseJson = await response.json();
const allItemsResp = await getAllItemsFromSpecificProject(
token,
responseJson.projectIds[0],
); // fetch called inside
return await allItemsResp.json();
} catch (error) {
console.log(error);
return null;
}
}
Both functions getCurrentUser and getAllItemsFromSpecificProject are simple fetch calls and are currently mocked properly. Here one test that tries to test the getAllItems function:
it('Gets all items', async () => {
getAccessTokenMockFetch();
const token = await getAccessToken('usherbrooke#powertree.io', 'test!23');
getAllItemsMockFetch();
const items = await getAllItems(token.response.access_token);
expect(items.response.length).toEqual(3);
});
For clarity, here is how getAccessTokenMockFetch is done. getAllItemsMockFetch is almost identical (with different data in the response):
function getAccessTokenMockFetch() {
global.fetch = jest.fn().mockImplementation(() => {
promise = new Promise((resolve, reject) => {
resolve(accepted);
});
return promise;
});
}
where accepted contains the JSON content of a successful call. When we run this test, we get the following error:
TypeError: Cannot read property 'response' of null
And we console.log this one in the catch:
TypeError: response.json is not a function
which explains why response is null. It seems the json() call is not understood and I don't know how to mock it. I have done tons of research on Stack Overflow and beyond, but we have found nothing that helps me understand how to solve this issue. This might indicate that I am going the wrong way about this, which is quite possible since I'm new to JavaScript, React Native, and Jest.
One thing to try is giving it a fake json to call, like this:
const mockFetch = Promise.resolve({ json: () => Promise.resolve(accepted) });
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
I am using node and axios (with TS, but that's not too important) to query an API. I have a suite of scripts that make calls to different endpoints and log the data (sometimes filtering it.) These scripts are used for debugging purposes. I am trying to make these scripts "better" by adding a delay between requests so that I don't "blow up" the API, especially when I have a large array I'm trying to pass. So basically I want it to make a GET request and pause for a certain amount of time before making the next request.
I have played with trying setTimeout() functions, but I'm only putting them in places where they add the delay after the requests have executed; everywhere I have inserted the function has had this result. I understand why I am getting this result, I just had to try everything I could to at least increase my understanding of how things are working.
I have though about trying to set up a queue or trying to use interceptors, but I think I might be "straying far" from a simpler solution with those ideas.
Additionally, I have another "base script" that I wrote on the fly (sorta the birth point for this batch of scripts) that I constructed with a for loop instead of the map() function and promise.all. I have played with trying to set the delay in that script as well, but I didn't get anywhere helpful.
var axios = require('axios');
var fs = require('fs');
const Ids = [arrayOfIds];
try {
// Promise All takes an array of promises
Promise.all(Ids.map(id => {
// Return each request as its individual promise
return axios
.get(URL + 'endPoint/' + id, config)
}))
.then((vals) =>{
// Vals is the array of data from the resolved promise all
fs.appendFileSync(`${__dirname}/*responseOutput.txt`,
vals.map((v) => {
return `${JSON.stringify(v.data)} \n \r`
}).toString())
}).catch((e) => console.log)
} catch (err) {
console.log(err);
}
No errors with the above code; just can't figure out how to put the delay in correctly.
You could try Promise.map from bluebird
It has the option of setting concurrency
var axios = require('axios');
var fs = require('fs');
var Promise = require('bluebird');
const Ids = [arrayOfIds];
let concurrency = 3; // only maximum 3 HTTP request will run concurrently
try {
Promise.map(Ids, id => {
console.log(`starting request`, id);
return axios.get(URL + 'endPoint/' + id, config)
}, { concurrency })
.then(vals => {
console.log({vals});
})
;
} catch (err) {
console.log(err);
}
I know there are tons of questions and answers on using Javascript promises to load returned Firebase objects, but I haven't succeeded in capturing the data in an array.
The issue: I've set up an array (productArray below) to iteratively take in values of a Firebase snapshot, but the array remains empty after exiting the forEach loop.
database.js:
getProductList = function(){
let productArray =[];
loadFBData = function(){
firebase.initializeApp(config);
return firebase.database().ref().once("value", function(snapshot){
return snapshot.val();
});
}
Promise.all([loadFBData()]).then(function(snapshot) {
snapshot.forEach(function(product) {
productArray.push(product.val());
});
});
}
Question: I think I'm using Promise.all wrong, but can't figure out how (also am new asynchronous functions). Thanks for any hints on how to get productArray to successfully load.
Edit: To elaborate, it seems snapshot is loading with the correct Firebase data, and the forEach is correctly looping through each item in snapshot but productArray becomes (or remains) empty outside of the Promise.all statement.
Update I implemented all the suggestions below and yet, the program will not stop long enough to build an array from the snapshot object. Last year I used Angularfire's $loaded function which was so effective in getting the program to wait so I don't understand why an ordinary JS promise won't work here. Does it make sense to resort to timeout? I even tried to combine alexmac's and Pointy's suggestions, but no cigar:
getProductList = function(){
let productArray =[];
loadFBData = function(){
firebase.initializeApp(config);
return new Promise(resolve => {
firebase.database().ref().once('value')
.then(function(snapshot){
return resolve(function(snapshot){
productArray = Object.keys(snapshot).map((key, prod) => prod);
});
});
});
}
loadFBData();
}
Background
I think you're missing an understanding of both promises and the Firebase SDK.
First, note that all Firebase SDK functions return promises, so there's no need to create your own. Second, Promise.all is for combining multiple promises. A single promises can be chained off of with a simple .then().
I have a video on saving and querying data here: Save and Query Firebase Data
I have another video on promises here: Promises
Loading Firebase
You should be initializing firebase at the very top of your page. It only needs to be initialized once, so don't initialize it within a function.
You can call firebase.initializeApp(config) just after loading the Firebase SDK on the page. Or you can reference Firebase Hosting's init.js as shown below.
Example
The following example loads the Firebase SDK, uses my testing db's init.js file to initialize firebase, then queries, manipulates and returns an array.
Note that Firebase doesn't support zero-indexed arrays, so everything you get back from Firebase will be an unsorted object. This example shows a few different ways of returning the data :)
<html>
<head>
<script src="https://www.gstatic.com/firebasejs/4.3.0/firebase.js"></script>
<script src="https://quiver-two.firebaseapp.com/__/firebase/init.js"></script>
<script>
function loadFirebaseArray(path) {
return firebase.database().ref(path).once('value').then(snap => snap.val());
}
function toArray(items) {
return Object.keys(items).reduce((array, key) => {
const value = items[key];
array.push({key, value});
return array;
}, []).sort();
}
loadFirebaseArray('stackoverflow').then(items => {
console.log('items', items);
const asArray = toArray(items);
alert(JSON.stringify(asArray));
const justTheValues = asArray.map(x => x.value).sort();
alert(justTheValues);
});
</script>
</head>
</html>
A working example might look like this:
getProductList = () => {
let loadFBData = () => {
firebase.initializeApp(config);
return new Promise(resolve => {
firebase.database().ref().once('value', resolve);
});
}
let productArray =[];
loadFBData()
.then(snapshot => {
productArray = Object.keys(snapshot).map((key, prod) => prod);
}
Here, I use promisification to create a promise for firebase query. So loadFBData returns a promise, I can create a promise chain or use ES7 async/await to wait until promise will be fulfilled to use result.