问题描述
我正在尝试使用 Node 从多个服务器检索 SFTP 列表。我正在使用 ssh2-sftp-client 库,并且我正在尝试使用 futzed Promise.all() 处理异步连接。
SFTP 服务器存储在一个配置文件 (servers.config
) 中,如下所示:
{
"myhost1": {
"host": "sftp.myhost1.com","port": "22 ","username": "userid1","password": "password1"
},"myhost2": {
"host": "sftp.myhost2.com","username": "userid2","password": "password2"
},...
}
我的代码看起来像这样...
#!/node
let fs = require('fs');
let Client = require('ssh2-sftp-client');
// which servers should we process?
const serverList = fs.readFileSync('./servers.config',{encoding:'utf8',flag:'r'});
const servers = JSON.parse(serverList);
const servers_to_process = Object.keys(servers);
function getDirectoryListing(config) {
let sftp = new Client();
sftp.connect(config)
.then(() => {
return sftp.list('/');
})
.then(data => {
console.log('Data retrieved for: ',config.host);
//console.log(data); // Line B
sftp.end();
return data;
})
.catch(err => {
console.log('Error for: ',config.host);
return [];
});
}
const processes_to_run = [];
// generate array of promises to run
servers_to_process.forEach( key => {
log('==========================');
log("Provider: "+key+"; "+timestamp);
processes_to_run.push(getDirectoryListing(servers[key]));
});
// wait for all the promises to resolve...
Promise.allSettled(processes_to_run).
then((results) => results.forEach((result) => console.log(result)));
我没有得到的是来自 A 行的任何控制台记录数据......但是如果我取消注释 B 行,我会异步获取每个列表。
输出看起来像这样:
JSON file read correctly
==========================
Provider: myhost1; 01/06/2021,14:57:25
==========================
Provider: myhost2; 01/06/2021,14:57:25
{ status: 'fulfilled',value: undefined }
{ status: 'fulfilled',value: undefined }
Data retrieved for: sftp.myhost1.com
Data retrieved for: sftp.myhost2.com
所以,很明显我在从 promise 返回数据时放弃了...
这是在处理之前将所有列表放入数组的正确方法吗?考虑到 SFTP 列表获取的异步性质,是否有更简洁的方法?
解决方法
您需要从函数中实际返回承诺 - getDirectoryListing()
不返回任何内容。因此,您将一个充满 undefined
的数组传递给 Promise.allSettled()
试试这个:
function getDirectoryListing(config) {
let sftp = new Client();
return sftp.connect(config)
.then(() => {
// ...stuff
}
,
您的 getDirectoryListing 实际上并未返回承诺。这样的事情应该对你有用:
#!/node
let fs = require('fs');
let Client = require('ssh2-sftp-client');
// which servers should we process?
const serverList = fs.readFileSync('./servers.config',{encoding:'utf8',flag:'r'});
const servers = JSON.parse(serverList);
const servers_to_process = Object.keys(servers);
//Ensure this is returning a promise by making it async
//and controlling the flow with await rather than callbacks
async function getDirectoryListing(config) {
let sftp = new Client();
await sftp.connect(config)
let list = await sftp.list('/');
console.log('Data retrieved for: ',config.host);
console.log(list); // Line B
sftp.end();
return list;
}
const processes_to_run = [];
// generate array of promises to run
servers_to_process.forEach( key => {
console.log('==========================');
console.log("Provider: "+key+"; "+Date.now());
processes_to_run.push(getDirectoryListing(servers[key]));
});
// wait for all the promises to resolve...
Promise.allSettled(processes_to_run).
then((results) => results.forEach((result) => console.log(result)));
,
要使您的代码正常工作,您只需要从函数 getDirectoryListing()
返回承诺以确保语句执行的正确顺序。
您的修复:
function getDirectoryListing(config) {
let sftp = new Client();
return sftp.connect(config) // just add a return here
// ...rest code will be same
}
但你也必须明白为什么会出现这种意想不到的结果。 (如果您想了解幕后发生的事情,请阅读本节)
当您调用方法 getDirectoryListing()
时,您将承诺添加到事件循环并返回 undefined
。由于 processes_to_run
数组充满了 undefined
,因此无法保证在 processes_to_run
数组中执行。这就是为什么执行首先进入console.log(result)
。?
// wait for all the promises to resolve...
Promise.allSettled(processes_to_run).
then((results) => results.forEach((result) => console.log(result)));
一旦事件循环完成了承诺,它就会将它们添加到回调队列中,然后进行处理。因此,A行在之后打印。?
sftp.connect(config)
.then(() => {
return sftp.list('/');
})
.then(data => {
console.log('Data retrieved for: ',config.host); // Line A
//console.log(data); // Line B
sftp.end();
return data;
})
.catch(err => {
console.log('Error for: ',config.host);
return [];
});
如果您想了解有关事件循环的更多信息,可以观看此amazing video。