如何在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.parse
和findFiles
是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)})); }
或者作为使用await
的async
函数:
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)}; }