如何在callback函数中访问app.get的'response'参数

我想通过一个谷歌驱动器API到EJS文件的文件列表( obj )。

即我想写

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

问题是我通过几个callback函数获取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本质上是asynchronous的,我所有的返回obj的尝试导致在提取obj之前检索它。

欢迎来到回拨地狱。 :-)旧的“节点”方式是做嵌套的callback,非常快速变得非常难看。

现代的方法是使用承诺,这使得更容易组合多个asynchronous操作。 使自己的asynchronous函数返回承诺,并为节点API函数(或不提供承诺的附加库),使用包装使它们承诺启用(手动,或通过使用像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.parsefindFiles是asynchronous的:

 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 }); }); 

如果函数需要一个参数并返回处理后的结果,那么使用非asynchronous函数then了,所以第一个版本也没有问题,尽pipe涉及到一些开销。

在这两种情况下, readFilePromise都是readFilePromise的promisified版本, 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; }); } 

(还要注意 – 主观性警告!因为我们不会以地狱般深嵌的callback结构为结束,所以我们可以使用一个合理的缩进宽度,而不是两个空间,所以很多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 } }); 

注意async before function和任何时候我们正在调用一个函数返回一个promise的await 。 一个async函数返回一个承诺,并在封面下await消费承诺。 代码看起来是同步的,但不是。 每await一个实际上是一个电话, then注册一个callback,当承诺完成。 同样, try / catch实际上是对promise链上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 } }); 

…但可读性/可debugging性受到影响。 🙂

重要注意事项:当一个async函数传入某个不需要函数返回promise的东西(比如app.get )的时候,你必须像上面那样把它封装在try / catch中,并处理任何错误,因为如果调用代码isn没有期待的承诺,它不会承诺拒绝,你需要这样做; 未处理的拒绝是一件坏事(在未来的Node版本中会导致你的进程终止)。

如果你传递一个async函数的确希望一个函数返回一个进程,最好把try/ catchclosures,并允许错误传播。


您需要findFiles帮助。 我build议学习promisify或类似的东西。 解决这个问题的正确方法是给自己一个drive.files.list的promisified版本,因为drive.files.list使用了Nodetypes的callback。

但是,如果没有实现它的话,我们可以这样做:

 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)})); } 

或者作为使用awaitasync函数:

 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)}; }