Я хочу передать список файлов (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 до его извлечения.
Добро пожаловать в аддон. :-) Старый способ "Узла" - это делать вложенные обратные вызовы, которые очень быстро угасают.
Современный подход заключается в использовании обещаний, что упрощает сбор нескольких асинхронных операций. Сделайте свои собственные функции 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)};
}
findFile
также асинхронна, будьте любезны, чтобы помочь мне обещать это тоже,
readdir
, вы пообещаете это, как мы делали readFile
выше, и readFile
обещание из него (возможно, с then
подключением к нему, если вам нужно преобразовать результаты).