Как получить доступ к параметру response в app.get в функциях обратного вызова

1

Я хочу передать список файлов (obj), через которые я получаю API-интерфейс Google для файлов EJS.

т.е. я хочу написать

app.get('/',function(req,res){
  res.render('index',obj);
}

Проблема в том, что я получаю объект js через несколько функций обратного вызова. Эта функция называется

fs.readFile('client_secret.json',processClientSecrets );

который, в свою очередь,

function processClientSecrets(err,content) {
if (err) {
  console.log('Error loading client secret file: ' + err);
  return;
}else{
  authorize(JSON.parse(content),findFiles);
 }
}

который называет эти два,

function authorise(credentials,callback) {
var clientSecret = credentials.installed.client_secret;
  var clientId = credentials.installed.client_id;
  var redirectUrl = credentials.installed.redirect_uris[0];
  var auth = new googleAuth();
  var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, function(err, token) {
    if (err) {
      getNewToken(oauth2Client, callback);
    } else {
      oauth2Client.credentials = JSON.parse(token);
      callback(oauth2Client);
    }
  });
}

[РЕДАКТИРОВАТЬ]

function findFiles(auth){
var obj ={};
var key = 'files';
obj[key]=[];
var drive = google.drive('v3');
drive.files.list({
                auth: auth,
                folderId: '****************',
                q: "mimeType contains 'application/pdf' and trashed = false"
                },
  function(err,response){
    var f = response.files;
    if (f.length == 0) {
    console.log('No files found.');
    }else {
        var i;
        for (i = 0; i < f.length; i++) {
        var file = f[i];
        //console.log('%s (%s)', file.name, file.id);
        obj[key].push(file.name + ' ' + file.id);
        }
        console.log(obj);
        return obj;
  }
                  });

}

Это похоже на очень простой вопрос, однако я не могу его решить, поскольку node.js является асинхронным по своей природе, и все мои попытки вернуть obj привели к рендерингу obj до его извлечения.

  • 0
    элемент управления достигает readFile, затем переходит к авторизации (я могу зарегистрировать объект, возвращенный авторизацией), но после этого он обнаруживает ошибку. :(
Теги:
google-api

1 ответ

1
Лучший ответ

Добро пожаловать в аддон. :-) Старый способ "Узла" - это делать вложенные обратные вызовы, которые очень быстро угасают.

Современный подход заключается в использовании обещаний, что упрощает сбор нескольких асинхронных операций. Сделайте свои собственные функции async, чтобы обещать обещания, а для функций Node API (или promisify которые еще не дают обещаний), используйте обертки, чтобы обезопасить их (вручную или с помощью чего-то вроде promisify).

Например, с помощью функций, основанных на обещаниях, ваш вызов будет выглядеть так:

app.get('/',function(req,res){
    readFilePromise('client_secret.json')
        .then(content => JSON.parse(content))
        .then(authorise)
        .then(findFiles)
        .then(files => {
            res.render('index', files);
        })
        .catch(err => {
            // Render error here
        });
});

или поскольку ни JSON.parse ни findFiles являются асинхронными:

app.get('/',function(req,res){
    readFilePromise('client_secret.json')
        .then(content => authorise(JSON.parse(content)))
        .then(auth => {
            res.render('index', findFiles(auth));
        })
        .catch(err => {
            // Render error here
        });
});

Прекрасно использовать не-асинхронные функции, then функция ожидает один параметр и возвращает обработанный результат, поэтому первая версия тоже прекрасна, хотя есть немного накладных расходов.

В обоих случаях readFilePromise является многообещающей версией readFile, и authorize выглядит примерно так:

function authorise(credentials) {
    var clientSecret = credentials.installed.client_secret;
    var clientId = credentials.installed.client_id;
    var redirectUrl = credentials.installed.redirect_uris[0];
    var auth = new googleAuth();
    var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

    // Check if we have previously stored a token.
    return readFilePromise(TOKEN_PATH)
        .then(token => {
            oauth2Client.credentials = JSON.parse(token);
            return oauth2Client;
        });
}

(Также обратите внимание - предупреждение субъективности! - что, поскольку мы не получаем адских глубоко вложенных структур обратного вызова, мы можем использовать разумную ширину отступов, а не два пространства, из-за которых многие программисты узла нуждались в принятии.)

Двигаясь дальше, если вы используете Node V8. x+, вы можете использовать синтаксис async/await для использования этих обещаний:

app.get('/', async function(req, res){
    try {
        const credentials = JSON.parse(await readFilePromise('client_secret.json'));
        const auth = await authorize(credentials);
        const files = findFiles(auth);
        res.render('index', files);
    } catch (e) {
        // Render error here
    }
});

Обратите внимание на function async перед function и await когда мы вызываем функцию, которая возвращает обещание. async функция возвращает обещание под обложками и await, await обезвреживает обещания под обложками. Код выглядит синхронно, но нет. Каждый await эффективно вызов then регистрации обратного вызова для того, когда обещание завершается. Точно так же try/catch является фактически вызовом метода catch в цепочке обещаний.

Мы могли бы конденсировать это, если бы хотели:

app.get('/', async function(req, res){
    try {
        res.render('index', findFiles(await authorize(JSON.parse(await readFilePromise('client_secret.json'))));
    } catch (e) {
        // Render error here
    }
});

... но читаемость/отладка страдают. :-)

Важное примечание. При передаче функции async во что-то (например, app.get), которая не ожидает, что функция вернет обещание, вы должны обернуть ее в try/catch как указано выше, и обработать любую ошибку, потому что, если вызывающий код isn Не ожидая обещания, он не будет обрабатывать обещания, и вам нужно это сделать; необработанные отказы - это плохая вещь (и в будущих версиях Узел приведет к завершению вашего процесса).

Если то, что вы передаете async функции , ожидает функция, возвращающая процесс, лучше оставить " try/ catch" и разрешить распространение ошибок.


Вы попросили помочь с findFiles. Я рекомендую изучать promisify или что-то подобное. Правильный путь (на мой взгляд), чтобы решить эту проблему, - дать себе многообещающую версию drive.files.list, поскольку drive.files.list использует обратные вызовы типа Node.

Но, не прощаясь, мы можем это сделать:

function findFiles(auth) {
    var drive = google.drive('v3');
    return new Promise(function(resolve, reject) {
        drive.files.list({
            auth: auth,
            folderId: '****************',
            q: "mimeType contains 'application/pdf' and trashed = false"
        },
        function(err, response) {
            if (err) {
                reject(err);
                return;
            }
            var f = response.files;
            if (f.length == 0) {
                console.log('No files found.');
            }
            else {
                var key = 'files'; // Why this indirection??
                resolve({[key]: f.map(file => file.name + ' ' + file.id)});
                // Without the indirection it would be:
                // resolve({files: f.map(file => file.name + ' ' + file.id)});
            }
        });
    });
}

Если бы у нас была обещанная версия, и мы покончили с key косвенностью, которая кажется ненужной, было бы проще:

function findFiles(auth) {
    return drivePromisified.files.list({
        auth: auth,
        folderId: '****************',
        q: "mimeType contains 'application/pdf' and trashed = false"
    }).then(files => ({files: files.map(file => file.name + ' ' + file.id)}));
}

Или как функция async используя await:

async function findFiles(auth) {
    const files = await drivePromisified.files.list({
        auth: auth,
        folderId: '****************',
        q: "mimeType contains 'application/pdf' and trashed = false"
    });
    return {files: files.map(file => file.name + ' ' + file.id)};
}
  • 0
    Сэр, спасибо за ваш ответ, на самом деле моя функция findFile также асинхронна, будьте любезны, чтобы помочь мне обещать это тоже,
  • 0
    @ AlwaysHungrie: хитро, как вы не показали его содержание. :-) Но в любом случае это лучшее упражнение для читателя: просто верните ему обещание. Например, если используется readdir , вы пообещаете это, как мы делали readFile выше, и readFile обещание из него (возможно, с then подключением к нему, если вам нужно преобразовать результаты).
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню