Search code examples
javascriptnode.jsasync-awaitgoogle-apies6-promise

Async / Await not working with Google drive API


I'm new to JavaScript and Async/Await concept.

I was working with the drive API example and was able to print out the files in my drive. But when I try to store the response into a variable called files the console is printing undefined.

I think the console.log(files) is printing before the response is generated?

Any idea how I can make the listFiles() function wait till the files variable is filled with the response so that I can send that data to my front end?


'use strict'

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');



// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.


class FileUploadController {

    index ({ request, response, view }) {
        fs.readFile('credentials.json', (err, content) => {
        if (err) return console.log('Error loading client secret file:', err);
        // Authorize a client with credentials, then call the Google Drive API.
        authorize(JSON.parse(content), listFiles);
        });

        /**
         * Create an OAuth2 client with the given credentials, and then execute the
         * given callback function.
         * @param {Object} credentials The authorization client credentials.
         * @param {function} callback The callback to call with the authorized client.
         */
        function authorize(credentials, callback) {
            const {client_secret, client_id, redirect_uris} = credentials.installed;
            const oAuth2Client = new google.auth.OAuth2(
                client_id, client_secret, redirect_uris[0]);

            // Check if we have previously stored a token.
            fs.readFile(TOKEN_PATH, (err, token) => {
                if (err) return getAccessToken(oAuth2Client, callback);
                oAuth2Client.setCredentials(JSON.parse(token));
                callback(oAuth2Client);
            });
        }

        /**
         * Get and store new token after prompting for user authorization, and then
         * execute the given callback with the authorized OAuth2 client.
         * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
         * @param {getEventsCallback} callback The callback for the authorized client.
         */
        function getAccessToken(oAuth2Client, callback) {
            const authUrl = oAuth2Client.generateAuthUrl({
                access_type: 'offline',
                scope: SCOPES,
            });
            console.log('Authorize this app by visiting this url:', authUrl);
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout,
            });
            rl.question('Enter the code from that page here: ', (code) => {
                rl.close();
                oAuth2Client.getToken(code, (err, token) => {
                if (err) return console.error('Error retrieving access token', err);
                oAuth2Client.setCredentials(token);
                // Store the token to disk for later program executions
                fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                    if (err) return console.error(err);
                    console.log('Token stored to', TOKEN_PATH);
                });
                callback(oAuth2Client);
                });
            });
        }

        let files;
        /**
         * Lists the names and IDs of up to 10 files.
         * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
         */
        async function listFiles(auth) {
            const drive = google.drive({version: 'v3', auth});
            // await drive.files.list({
            //     pageSize: 10,
            //     fields: 'nextPageToken, files(id, name)',
            // }, (err, res) => {
            //     if (err) return console.log('The API returned an error: ' + err);                
            //     files = res.data.files;
            //     console.log(files)
            //     if (files.length) {
            //     console.log('Files:');
            //     files.map((file) => {
            //         console.log(`${file.name} (${file.id})`);
            //     });
            //     } else {
            //     console.log('No files found.');
            //     }
            // });
            drive.files.list({
                pageSize: 10,
                fields: 'nextPageToken, files(id, name)',
            }).then( res => {
                files = res.data.files
                console.log(files);
            })
        }

        console.log(files);



    return view.render('file-upload', {files: 'files are here'})

    }


}

module.exports = FileUploadController


when this controller is run my terminal shows the below

info: serving app on http://127.0.0.1:3333
undefined
[ { id: '1Pg5KODwcdlG7oV64pOSl9tBMtzy_p498', name: 'photo.jpg' },
  { id: '1_u248zVI2swg0KLMaL9oRERkGUNHbV2u', name: 'photo.jpg' },
  { id: '1duLnJrPYkd75ZNFZx0OvTUPYYmZAaNzS', name: 'photo.jpg' },
  { id: '1TJC6-VfuusA6rXgtC9E3WQQsFg4JZJkn',
    name: 'etosEntityDiagram.drawio' },
  { id: '1XUg_Xju4hZr2KTEsEt8WHz0bAz8isAEo',
    name: 'Untitled Diagram.drawio' },
  { id: '14MsZFOZGLZqEZyNbI0yYTo4I4aW0gK7K',
    name: 'ProjectFileManager.drawio' },
  { id: '0B3g4WnyGCaYQdExQSm9ZYmkwRTQ',
    name: 'High Elves - 8th Edition.pdf' },
  { id: '1ok4WdMaalXWd9y0PGENewMlElPUxEqTNY3p8wwch_h4',
    name: 'Google VM Pricing' },
  { id: '1GhRSL8y9jzm8eefwFFvCp2AuJhtGUOEx', name: 'gcp.drawio' },
  { id: '1OC_D8CNe7CXzzkJpMNYKkVT1KteS2vQulov2E-D5KNs',
    name: 'Bucket Pricing Example' } ]



Solution

  • Sorry, I didn’t really understand what exactly you wanted to do, use async/await or just understand how to transfer files in any way to the render method. I redid your code a bit on async/await, I could not check it, since you did not provide a server. I hope it brings some clarity to your understanding of how to work with async/await:

    'use strict'
    
    const fs = require('fs');
    const util = require('util');
    const readline = require('readline');
    const { google } = require('googleapis');
    
    const fsp = fs.promises;
    
    readline.Interface.prototype.question[util.promisify.custom] = function(prompt) {
        return new Promise(resolve =>
            readline.Interface.prototype.question.call(this, prompt, resolve),
        );
    };
    
    readline.Interface.prototype.questionAsync = util.promisify(readline.Interface.prototype.question);
    
    // If modifying these scopes, delete token.json.
    const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
    // The file token.json stores the user's access and refresh tokens, and is
    // created automatically when the authorization flow completes for the first
    // time.
    const TOKEN_PATH = 'token.json';
    
    class FileUploadController {
    
        async index({ request, response, view }) {
            try {
                const files = await this.getFiles();
                return view.render('file-upload', { files });
            } catch (error) {
                // ... you must handle the error
            }
        }
    
        async getFiles() {
            try {
                // Load client secrets from a local file.
                const content = await fsp.readFile('credentials.json');
                const oAuth2Client = await this.authorize(JSON.parse(content));
                const files = await this.listFiles(oAuth2Client);
                return files;
            } catch (error) {
                console.error('Error loading client secret file:', error);
                throw error;
            }
        }
    
        /**
         * Create an OAuth2 client with the given credentials, and then execute the
         * given callback function.
         * @param {Object} credentials The authorization client credentials.
         */
        async authorize(credentials) {
            const { client_secret, client_id, redirect_uris } = credentials.installed;
            const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
    
            // Check if we have previously stored a token.
            try {
                const token = await fsp.readFile(TOKEN_PATH);
                oAuth2Client.setCredentials(JSON.parse(token));
                return oAuth2Client;
            } catch (error) {
                console.error(error);
                return this.getAccessToken(oAuth2Client);
            }
        }
    
        /**
         * Get and store new token after prompting for user authorization, and then
         * execute the given callback with the authorized OAuth2 client.
         * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
         * @param {getEventsCallback} callback The callback for the authorized client.
         */
        async getAccessToken(oAuth2Client) {
            const authUrl = oAuth2Client.generateAuthUrl({
                access_type: 'offline',
                scope: SCOPES,
            });
    
            console.log('Authorize this app by visiting this url:', authUrl);
    
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout,
            });
    
            const code = await rl.questionAsync('Enter the code from that page here: ');
            rl.close();
    
            try {
                const token = await oAuth2Client.getToken(code);
                oAuth2Client.setCredentials(token);
                // Store the token to disk for later program executions
                await fsp.writeFile(TOKEN_PATH, JSON.stringify(token));
                return oAuth2Client
            } catch (error) {
                console.error('Error retrieving access token', error);
                throw error;
            }
        }
    
        /**
         * Lists the names and IDs of up to 10 files.
         * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
         */
        async listFiles(auth) {
            try {
                const drive = google.drive({ version: 'v3', auth });
                const response = await drive.files.list({
                    pageSize: 10,
                    fields: 'nextPageToken, files(id, name)',
                });
                return response.data.files;
            } catch (error) {
                console.error('The API returned an error:', error);
                throw error;
            }
        }
    
    }
    
    module.exports = FileUploadController;
    

    I don't claim that this code is written 100% well, but it displays exactly how to work with async/await and how to get the files variable in order to pass it to render.