NodeJS:等待所有带有 Promises 的 foreach 完成,但从未真正完成
Posted
技术标签:
【中文标题】NodeJS:等待所有带有 Promises 的 foreach 完成,但从未真正完成【英文标题】:NodeJS: Wait for all foreach with Promises to finish but never actually finishes 【发布时间】:2019-11-01 23:13:07 【问题描述】:我正在使用 Nodejs。我有一个异步的 forEach,因为我必须在 forEach 中等待结果。结果,我需要等待 forEach 完成,然后继续循环的结果。我找到了几种等待 forEach 的解决方案,其中之一是使用 Promises。我确实这样做了,并且创建了这些承诺,但是,在 forEach(以及承诺)完成之后的代码从未真正执行过(console.log 未打印)。 NodeJS 函数刚刚结束,没有任何错误。
这是我的代码:
var Client = require('ssh2').Client;
// eslint-disable-next-line no-undef
var csv = require("csvtojson");
// eslint-disable-next-line no-undef
var fs = require("fs");
// eslint-disable-next-line no-undef
const config = require('./config.json');
// eslint-disable-next-line no-undef
const os = require('os');
let headerRow = [];
let sumTxAmount = 0;
const filenameShortened = 'testFile';
let csvLists = [];
let csvFile;
const options =
flags: 'r',
encoding: 'utf8',
handle: null,
mode: 0o664,
autoClose: true
var conn = new Client();
async function start()
const list = await getCSVList();
let content = fs.readFileSync('./temp.json', 'utf8');
content = JSON.parse(content);
var promises = list.map(function(entry)
return new Promise(async function (resolve, reject)
if (!content['usedFiles'].includes(entry.filename))
const filename = entry.filename;
csvFile = await getCsv(filename);
csvLists.push(csvFile);
console.log('here');
resolve();
else
resolve();
)
);
console.log(promises)
Promise.all(promises)
.then(function()
console.log(csvLists.length, 'length');
)
.catch(console.error);
start();
“这里”打印一次(不是 8 次,因为数组长度为 8),但创建了 8 个 promise。我打印数组长度的下部未执行。
谁能告诉我我做错了什么?我是否错误地使用了 Promises 和 forEach,因为我必须在 forEach 中进行等待?
注意:getCSVList() 和 getCsv() 是从 sftp 服务器获取 Csv 的函数:
function getCSVList()
return new Promise((resolve, reject) =>
conn.on('ready', function ()
conn.sftp(function (err, sftp)
if (err) throw err;
sftp.readdir(config.development.pathToFile, function (err, list)
if(err)
console.log(err);
conn.end();
reject(err);
else
console.log('resolved');
conn.end();
resolve(list);
)
)
).connect(
host: config.development.host,
port: config.development.port, // Normal is 22 port
username: config.development.username,
password: config.development.password
// You can use a key file too, read the ssh2 documentation
);
)
function getCsv(filename)
return new Promise((resolve, reject) =>
conn.on('ready', function ()
conn.sftp(function (err, sftp)
if (err) reject(err);
let csvFile = sftp.createReadStream(`$config.development.pathToFile/$filename`, options);
// console.log(csvFile);
conn.end();
resolve(csvFile);
)
).connect(
host: config.development.host,
port: config.development.port, // Normal is 22 port
username: config.development.username,
password: config.development.password
// You can use a key file too, read the ssh2 documentation
);
);
所有控制台日志在我的控制台中的输出是:
`➜ node server.js
resolved
[ Promise <pending> ,
Promise <pending> ,
Promise <pending> ,
Promise <pending> ,
Promise <pending> ,
Promise <pending> ,
Promise <pending> ,
Promise <pending> ]
here`
【问题讨论】:
可能getCsv
会比getCSVList
更有趣,因为这似乎是它被屏蔽的地方。
@JeffRSon 我现在添加了它
我认为您并没有真正从流中阅读。您应该订阅其“数据”事件或将其通过管道传输到可写流。可能在结束连接之前。
var csvLists
定义在哪里?我试图通过在 start()
函数范围内声明 const csvLists = []
在本地重现您的代码,它工作得很好......
@k0pernikus 我做了,但后来用户要求更多代码。所以我添加了它
【参考方案1】:
将您的问题分解为多个部分,并确认它们一直有效。
除其他外,您没有正确使用流。
我用ssh2-sftp-client
做了一个工作示例,因此您可以将其用作起点。
工作示例:
var fs = require('fs'); var _ = require('underscore');
var SFTPClient = require('ssh2-sftp-client');
const CONFIG =
"SSH_CONN_OPTS":"host":"XXXXXXXX","port":22,"username":"XXXXXXXX","password":"XXXXXXXX",
"CSV_DIRECTORY":"/var/www/html"
//---------------
//.:The order-logic of the script is here
function StartScript()
console.log("[i] SSH Connection")
LoadValidationFile(()=>
InitializeSFTP(()=> console.log("[+] SSH Connection Established")
ListRemoteDirectory((list)=> console.log(`[i] Total Files @ $CONFIG.CSV_DIRECTORY : $list.length`)
//console.log(list) //:now you have a 'list' of file_objects, you can iterate over to check the filename
var csvFileList = [] //store the names of the files you will request after
_.each(list,(list_entry)=> console.log(list_entry)
if(!CONFIG.USED_FILES.includes(list_entry.name)) csvFileList.push(list_entry.name)
)
//:now loop over the new final list of files you have just validated for future fetch
GenerateFinalOutput(csvFileList)
)
)
)
//.:Loads your validation file
function LoadValidationFile(cb)
fs.readFile(__dirname+'/temp.json','utf8',(err,data)=> if(err)throw errelse
var content = JSON.parse(data)
CONFIG.USED_FILES = content.usedFiles
cb()
)
//.:Connects to remote server using CONFIG.SSH_CONN_OPTS
function InitializeSFTP(cb)
global.SFTP = new SFTPClient();
SFTP.connect(CONFIG.SSH_CONN_OPTS)
.then(()=>cb())
.catch((err)=>console.log("[!] InitializeSFTP :",err))
//.:Get a list of files from a remote directory
function ListRemoteDirectory(cb)
SFTP.list(`$CONFIG.CSV_DIRECTORY`)
.then((list)=>cb(list))
.catch((err)=>console.log("[!] ListRemoteDirectory :",err))
//.:Get target file from remote directory
function GetRemoteFile(filename,cb)
SFTP.get(`$CONFIG.CSV_DIRECTORY/$filename`)
.then((data)=>cb(data.toString("utf8"))) //convert it to a parsable string
.catch((err)=>console.log("[!] ListRemoteDirectory :",err))
//-------------------------------------------
var csvLists = []
function GenerateFinalOutput(csv_files,current_index) if(!current_index)current_index=0
if(current_index!=csv_files.length) //:loop
var csv_file = csv_files[current_index]
console.log(`[i] Loop Step #$current_index+1/$csv_files.length : $csv_file`)
GetRemoteFile(csv_file,(csv_data)=>
if(csv_data)csvLists.push(csv_data)
current_index++
GenerateFinalOutput(csv_files,current_index)
)
else //:completed
console.log("[i] Loop Completed")
console.log(csvLists)
//------------
StartScript()
祝你好运!
【讨论】:
【参考方案2】:Promise.all
是一个将返回一个 promise 对象的方法,但您不会等待 start 方法执行。
function getCSVList()
return new Promise((resolve, reject) =>
setTimeout(() =>
resolve([1, 2, 3, 4]);
, 1000);
);
function getCsv(params)
return new Promise((resolve, reject) =>
setTimeout(() =>
resolve(params);
, 1000);
);
async function start()
const list = await getCSVList();
const promises = list.map(item =>
return new Promise(async function (resolve, reject)
const csvFile = await getCsv(item);
console.log('here');
resolve(csvFile);
);
);
return Promise.all(promises);
start().then(res =>
console.log(res);
);
【讨论】:
感谢您的想法。我用我的代码试过了,但是“res”从来没有打印出来 您可以尝试将 csvFile 放入 resolve 方法中。就像这样 'resolve(csvFile)' 您不需要将test
包装在新的承诺中。它已经是一个异步函数。你可以做list.map(test)
。然后你可以直接return Promise.all(list.map(test))
.以上是关于NodeJS:等待所有带有 Promises 的 foreach 完成,但从未真正完成的主要内容,如果未能解决你的问题,请参考以下文章
在循环中使用带有 fs.readFile 的 Promises
带有 JavaScript 函数的 Async-Await 或 Promises [关闭]