Search code examples
javascripthtmlfirebasespotify

Ensuring the program retrieves the credentials from the database BEFORE setting them


I am relatively new to Javascript and promises so I am not sure how to ensure that the code follows a certain order in this scenario.

Fundamentally, I need to retrieve the credentials from the database before setting them. How do I ensure that the retrieval code happens before the setting code - is there a way to use async and await to do this?

Code that needs to be completed first starts with **admin.firestore().collection('credentials').get().then(async (snapshot) => {**

Code that needs to come second is

**client_id: Credentials.client_id,
        client_secret: Credentials.client_secret**

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`
});


admin.firestore().collection('credentials').get().then(async (snapshot) => {
    await snapshot.docs.forEach(doc => {
        console.log(JSON.stringify(doc.data().client_id));
        // Credentials.client_id = JSON.stringify(doc.data().client_id);
        console.log(JSON.stringify(doc.data().client_secret));
        // Credentials.client_secret = JSON.stringify(doc.data().client_secret);
        let client_id = JSON.stringify(doc.data().client_id);
        let client_secret = JSON.stringify(doc.data().client_secret);


        const regex = /(\w)+/g;

        let m;
        let n;
        while ((m = regex.exec(client_id)) !== null) {
            // This is necessary to avoid infinite loops with zero-width matches
            if (m.index === regex.lastIndex) {
                regex.lastIndex++;
            }

            // The result can be accessed through the `m`-variable.
            m.forEach((match, groupIndex) => {
                Credentials.client_id = match;
                console.log(`Found match, group ${groupIndex}: ${match}`);

            });
        }
        while ((n = regex.exec(client_secret)) !== null) {
            // This is necessary to avoid infinite loops with zero-width matches
            if (n.index === regex.lastIndex) {
                regex.lastIndex++;
            }

            // The result can be accessed through the `n`-variable.
            n.forEach((match, groupIndex) => {
                Credentials.client_secret = match;
                console.log(`Found match, group ${groupIndex}: ${match}`);

            });
        }
    });


});

class Credentials {

    constructor(client_id, client_secret) {
        this.client_id = client_id;
        console.log('Id in class ' + this.client_id);
        this.client_secret = client_secret;
        console.log('Secret in class ' + this.client_secret);

    }

}

/**
 * ----------------------Below section of code found at: LINK TO GIT REPOSTITORY---------------------------------
 */
// Spotify OAuth 2 setup
const SpotifyWebApi = require('spotify-web-api-node');
const Spotify = new SpotifyWebApi({
    
    client_id: Credentials.client_id,
    client_secret: Credentials.client_secret,
    redirectUri: `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`
});

I am getting either an [object Promise] or undefined when the program runs. But on the console logs, a few seconds after it logs the data that I need. Really stuck on this one - anyone know how to solve this? (code below is its current state which leads to the [object Promise] situation.

client_id: Promise.resolve(admin.firestore().collection('credentials').get().then(async (snapshot) => {
        await snapshot.docs.forEach(doc => {
            JSON.stringify(doc.data().client_id);})})),
    client_secret: (admin.firestore().collection('credentials').get().then(async (snapshot) => {
        await snapshot.docs.forEach(doc => {
            JSON.stringify(doc.data().client_secret);})})),

Solution

  • You are already retrieving the the credentials first with this line

    admin.firestore().collection('credentials').get()
    

    This function returns a Promise which you can either continue on with then, as you already have, or use async/await to retrieve the value from the function.

    Promises & Async/Await

    Use it as a Promise with then.

    await admin.firestore().collection('credentials').get().then((snapshot => {
        // Credentials have been retrieved.
        // Continue here to use the credentials.
    });
    

    Or use it in a async/await function.

    async function getCredentials() {
        const snapshot = await admin.firestore().collection('credentials').get();
        return snapshot;
    }
    

    Both will return a Promise but the choice is up to you.

    Now there are a couple of things that have to be changed in your code, either being in the wrong position or using it in a faulty manner.

    Credentials

    You've created a class called Credentials. While the class definition is correct the instantiation is not. To create a new instance of a class use the new keyword. A class is virtually a blueprint of an object which can be created at any point in the code with the new keyword.

    const client_id = 'yourclientid';
    const client_secret = 'yourclientsecret';
    const credentials = new Credentials(client_id, client_secret);
    
    // {
    //     client_id: 'yourclientid',
    //     client_secret: 'yourclientsecret'
    // }
    

    Placement

    Because admin.firestore().collection('credentials').get() is an asynchronous function it will not stop the processing of the file. The rest of file will be executed without waiting for the retrieval of the credentials. But you want to do something with the credentials after they've been retrieved. You can do that with the then method of the Promise. then is called whenever a Promise has resolved. Meaning that it is finished waiting and can continue.

    So the Spotify part has to be inside the then method whenever the credentials have been retrieved.

    admin.firestore().collection('credentials').get().then((snapshot) => {
        // We are in the then method, so credentials have been retrieved.
    
        snapshot.docs.forEach(doc => {
           // Check the credentials...
           // ...
    
           // Create your credentials object. 
           const credentials = new Credentials(client_id, client_secret);
    
           // Create new Spotify session.
           const Spotify = new SpotifyWebApi({
               client_id: credentials.client_id,
               client_secret: credentials.client_secret,
               redirectUri: `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`
           });
    
        });
    });
    

    Require

    Not that it will matter a whole lot, but it is good practice to put your require statements on the top of the page. This makes it clear for you, or any other that reads code, what files or dependencies are included in this file.

    // Put me at the top of the page.
    const SpotifyWebApi = require('spotify-web-api-node');
    

    Conclusion

    This is all what I can tell from looking through your code. You are on the right track but still need to read up more on how Promises work and more importantly why and when do you use them.

    Create the Spotify session inside the then method callback and use the new keyword to create an instance of your Credentials class.