Nodejs Stream chaining promises write only the first one - node.js

i'm trying to make a Stream that is writing to multiples files using an async Generator.
Using reduce promises are executed sequentially so the desired output is :
$ wc -l *
42 f1.csv
98 f2.csv
78 f3.csv
but i'm getting the following result :
$ wc -l *
42 f1.csv // only the first one is good
0 f2.csv // nothing in
0 f3.csv // same
AsyncGenerator() is a ElasticSearch Scroll Search (Doc)
I send him different query on each promise.
const { Readable } = require('stream');
const fs = require('fs');
const util = require('util');
const stream = require('stream');
const pipeline = util.promisify(stream.pipeline);
async function* Query(params) {
var response = await ESClient.search(params);
while (true) {
const sourceHits = response.hits.hits;
if (sourceHits.length === 0) {
break;
}
for (const hit of sourceHits) {
yield hit;
}
if (!response._scroll_id) {
break;
}
response = await ESClient.scroll({
scrollId: response._scroll_id,
scroll: params.scroll
});
}
}
async function* AsyncGenerator() {
const params = {
index: 'apache-logs',
scroll: '5s',
_source: ["foo"],
size: 2048,
body: { query } // ElasticSearch DSL Query
};
for await (const res of Query(params)) {
yield res;
}
}
async function WriteToFile(file) {
return new Promise(async (resolve, reject) => {
const input = Readable.from(AsyncGenerator());
const output = fs.createWriteStream(file + '.csv');
await pipeline(
input,
json2csv,
output
);
resolve();
});
}
const lists = ["f1", "f2", "f3"];
const last = lists.reduce(async (previous, next) => {
await previous;
return WriteToFile(next);
}, Promise.resolve());
last.then(_ => {
console.log("DONE");
}).catch((e) => {
console.log(e);
});
(Lorem ipsum lalalaalalal (sry stackoverflow is asking to provide more details but i dont have lol))

Hi I am not sure because my acquaintances are poor in NodeJs. I think you are only calling one time to the function which is creating your .csv
const lists = ["f1", "f2", "f3"];
const last = lists.reduce(async (previous, next) => {
await previous;
return WriteToFile(next);
}, Promise.resolve());

Related

async/await troubles in a recursive Redis function

Ima rookie using async/await but must now to use Redis-om. NN_walkd walks through a Redis database looking for loop-chains and does this by recursion. So the 2 questions I have is:
Am I calling the inner recursive NN_walkd calls correctly via async/await?
At runtime, the compSearchM proc is called first and seems to work (it gets 5 entries so it has to call NN_walkd 5 times). A NN_walkd is then recursively called, and then when it loops the 1st time it then calls compSearchK where the problems are. It seems to sit on the first Redis call in compSearchK (.search). Yet the code for compSearchK and compSearchM look basically identical.
main call
NN_walk = async function(req, db, cnode, pnode, chain, cb) {
var vegas, sneaker;
req.session.walk = [];
await NN_walkd(req, cnode, pnode, [], 1);
req.session.walk = null;
console.log('~~~~~~~~~~~~ Out of Walk ~~~~~~~~~~~~~~~');
cb();
};
redis.mjs
export class RedisDB {
constructor() {
...
this._companyRepo = ...
}
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
compSearchM(mkey) {
var tArr=[];
return new Promise(async (resolve) => {
const smkey = await this._companyRepo.search()
.where('MASTERKEY').equals(mkey)
.and('TBLNUM').equals(10)
.return.all();
if (smkey.length) {
for (var spot in smkey) {
const ttrr = await this._companyRepo.fetch(smkey[spot].entityId);
tArr.push(ttrr.toJSON());
}
resolve(tArr);
} else {
resolve(null);
}
});
}
walk.js
NN_walkd = async function(req, cnode, pnode, chain, lvl) {
...
if (cnode[1]) {
const sObj = await req.app.get('redis').compSearchK(cnode[1]);
if (sObj) {
int1 = (sObj.TBLNUM==1) ? null : sObj.CLIENTKEY;
(async () => await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl))()
}
} else {
const sArr = await req.app.get('redis').compSearchM(cnode[0]);
if (sArr.length) {
for (sneaker in sArr) {
(async () => await NN_walkd(req, [sArr[sneaker].COMPANYKEY,sArr[sneaker].CLIENTKEY], cnode, Array.from(chain), tlvl))()
}
} else {
console.log('no more links on this chain: ',cnode);
}
}
}
"doesn't matter if i have async or not here"
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
Of course it doesn't matter, because you're not using await inside of compSearchK!
You are using the explicit promise contructor anti-pattern. You should avoid it as it demonstrates lack of understanding. Here is compSearchK rewritten without the anti-pattern -
async compSearchK(ckey) {
// await key
const sckey =
await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
// return null if key is not found
if (sckey.length == 0) return null;
// otherwise get ttrr
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
// return ttrr as json
return ttrr.toJSON();
}

How to use dynamoDB batchWriteItem with nodejs sdk?

I have a use case where I need to perform a batch_write operation on dynamodb. I referred this article which has a good solution for similar use case. I implemented it with few cleanup in my code and it works as expected.
const _ = require('lodash');
// helper methods
async function asyncForEach(array, cb) {
await Promise.all(array.map(async (item) => {
await cb(item, array);
}));
}
function to(promise) {
return promise.then((data) => [null, data])
.catch((err) => [err]);
}
const call = function (params) {
return dynamoDb.batchWriteItem(params).promise();
};
async function batchWrite25(arrayOf25, tableName) {
// 25 is as many as you can write in one time
const itemsArray = [];
_.forEach(arrayOf25, (item) => {
itemsArray.push({
PutRequest: {
Item: item,
},
});
});
const params = {
RequestItems: {
[tableName]: itemsArray,
},
};
await to(call(params));
}
async function batchWrite(itemArray, tableName) {
let mainIndex = 0;
let subIndex = 0;
let arrayOf25 = [];
const arrayLength = itemArray.length;
await asyncForEach(itemArray, async (item) => {
arrayOf25.push(item);
subIndex += 1;
mainIndex += 1;
// 25 is as many as you can write in one time
if (subIndex % 25 === 0 || mainIndex === arrayLength) {
await to(batchWrite25(arrayOf25, tableName));
subIndex = 0; // reset
arrayOf25 = [];
}
});
}
module.exports = {
batchWrite,
};
However, the code looks a bit complicated here with so many callbacks involved. Is there a cleaner way of writing the same thing without using -- call or asyncForEach or to methods ?
Here's one simple way to batch the items:
const BATCH_MAX = 25;
const batchWrite = async (items, table_name) => {
const BATCHES = Math.floor((items.length + BATCH_MAX - 1) / BATCH_MAX);
for (let batch = 0; batch < BATCHES; batch++) {
const itemsArray = [];
for (let ii = 0; ii < BATCH_MAX; ii++) {
const index = batch * BATCH_MAX + ii;
if (index >= items.length) break;
itemsArray.push({
PutRequest: {
Item: items[index],
},
});
}
const params = {
RequestItems: {
[table_name]: itemsArray,
},
};
console.log("Batch", batch, "write", itemsArray.length, "items");
await dynamodb.batchWriteItem(params).promise();
}
};
To make the entire process asynchronous, you can convert this function to return an array of promises and later call Promise.all(promises) on that array. For example:
const batchWrite = (items, table_name) => {
const promises = [];
const BATCHES = Math.floor((items.length + BATCH_MAX - 1) / BATCH_MAX);
for (let batch = 0; batch < BATCHES; batch++) {
// same code as above here ...
promises.push(dynamodb.batchWriteItem(params).promise());
}
return promises;
};
A much cleaner way using lodash that worked for me is listed below. Hope this helps somone.
batchWrite=async ()=> {
const batchSplitArr=_.chunk(this.dynamoPayload,25); //dynamoPayload has the entire payload in the desired format for dynamodb insertion.
await Promise.all(
batchSplitArr.map(async (item) => {
const params = {
RequestItems: {
[this.tableName]: item,
},
};
await this.dynamoDb.batchWriteItem(params).promise();
})
);
};

nodejs calling database calls parallel

The module calls three tables(students, subjects, grades)I call three sql queries and somehow managed to call one by one as explained below. the queries are independent of each other. However the select queries are executed one by one, first student, then subjects, then grades using await.
studentmodel.calls are only for executing select quesries from the database and is in one module. Other functions are defined in a separate module
The logic can execute the three selct queries(database calls) in parallel, then aggregate and process all the data together. Please let me know how to modify so that the database calls can execute independent, then move to process all data together
processing module -main start call
const mainstart = async () => {
let students = 0;
const getStudentData = await getAllStudents();
/** checking a condition if getEmployeeData responce is not empty */
if (getStudentData.length > 0) {
const studentData = await processData(getStudentData);
return 1;
} else {
return 0;
}
};
same file secondcall to the function getAllStudents
const getAllStudents = async () => {
try {
return await studentmodel.getAllStudents();//database call 1
} catch (err) {
// console.log("processing", err)
}
};
const processData = async (getStudentData) => {
try {
let studentData = [];
const subjectsData = await studentModel.getStudentSubjects();//database call 2
const gradesData = await studentModel.getStudentGrades();//database call 3
await Promise.all(getStudentData.map(async (singleObject) => {
let subjects = await processSubjects(subjectsData, singleObject.student_log);
let grades = await processGrades(gradesData, singleObject.student_log);
//Some processing on sigleobject, subjects and grades to populate studentData array
}));
return studentData;
} catch (err) {
console.log("processing", err)
}
};
const processSubjects = async (result, Name) => {
let subjectsArr = [];
const processArray = result.filter(ele => ele.student_log == Name)
processArray.map((singleObject) => {
subjectsArr.push({
name: singleObject.name,
startDate: singleObject.startDate,
});
})
return subjectsArr;
}
const processGrades = async (result, Name) => {
let gradesArr = [];
const processArray = result.filter(ele => ele.student_log == Name)
processArray.map((singleObject) => {
gradesArr.push({
id: singleObject.id,
name: singleObject.name,
});
})
return gradesArr;
database calls module/studentModel
const getAllStudents = async () => {
try {
/** Populating all students */
const sqlQuery = `SELECT * FROM STUDENTS`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
const getStudentSubjects = async () => {
try {
/** Populating all skills */
const sqlQuery = `SELECT * FROM Subjects`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
const getStudentGrades = async () => {
try {
/** Populating all students */
const sqlQuery = `SELECT * FROM GRADES`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
While I didn't probably fully understand what your question is, I had a go with your code.
I simulated your studentmodel functions with setTimeout and made the code like so that it first fetches all students. After fetching all students, it fetches the subjects and the grades "in parallel" by utilising Promise.all. After we have fetched our students, subjects and grades, we pass all of those to processData function where you can process all of the data however you want.
In case you would also like to fetch the students "in parallel" with the subjects and grades, just change the Promise.all part like so:
const [studentData, studentSubjects, studentGrades] = await Promise.all(
[
getAllStudents(),
getStudentSubjects(),
getStudentGrades()
]);
And remove the const studentData = await getAllStudents(); line and the if-clause. Because you had the if(studentData.length > 0) in your code, I assumed that we only want to fetch subjects and grades if there are students and therefore that needs to be done first, separately.
Note that if you want to do all three in parallel, you cannot use studentData when calling getStudentSubjects or getStudentGrades.
// STUDENTMODEL
const getAllStudents = async () => {
// Simulate SQL query
console.log("Fetching students");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Student 1", "Student 2"])
}, 1000));
};
const getStudentSubjects = async () => {
// Simulate SQL query
console.log("Fetching subjects");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Subject 1", "Subject 2"])
}, 1500));
};
const getStudentGrades = async () => {
// Simulate SQL query
console.log("Fetching grades");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Grade 1", "Grade 2"])
}, 1500));
};
const mainstart = async () => {
// Fetch student data from database
const studentData = await getAllStudents();
if (studentData.length > 0) {
// Use Promise.all to wait until both student subjects and
// student grades have been fetched
// The results are destructured into
// studentSubjects and studentGrades variables
const [studentSubjects, studentGrades] = await Promise.all(
[
getStudentSubjects(studentData),
getStudentGrades(studentData)
]);
// Everything is fetched, process it all
processData([studentData, studentSubjects, studentGrades]);
return 1;
} else {
return 0;
}
};
const processData = (allData) => {
console.log("Processing all data");
console.log(allData);
// Process data somehow
};
(async () => {
console.log('start');
await mainstart();
console.log('end');
})();

Get value out of function in Node.js

I don't know about the correct title for my problem. I just need a value going out of a function, just like return but I think it's not same.
i have code snippet from controller in adonisjs framework:
var nmeadata = "";
jsondata.forEach(element => {
var x = element.nmea
var buff = new Buffer(x, 'base64')
zlib.unzip(buff, (err, res) => {
if(err)
{
//
}
else
{
nmeadata += "greed island"
nmeadata += res.toString()
}
})
});
return view.render('admin.index', {
data: datanmea.toJSON(),
nmea: nmeadata
})
I need the result of unzipped string data that inserted to nmeadata from zlib function then send it to view. But, for this time, even I cannot displaying a simple output like greed island to my view.
thank you.
UPDATE
Still not working after using promises:
class NmeaController {
async index({view})
{
const datanmea = await NmeaModel.all()
const jsondata = datanmea.toJSON()
var promises = [];
var nmeadata = "";
jsondata.forEach(element => {
promises.push(
new Promise(resolve => {
let x = element.nmea
let buff = new Buffer(x, 'base64')
zlib.unzip(buff,
(err, res) => {
if (err) {
//
} else {
nmeadata += "test add text"
// nmeadata += res.toString()
}
//im also try using resolve() and resolve("any text")
resolve(nmeadata);
})
}
)
)
});
await Promise.all(promises);
return view.render('admin.index', {
data: datanmea.toJSON(),
nmea: nmeadata
});
}
UPDATE AUGUST 22 2019
i'm already tried solution from maksbd19 but still not working
class NmeaController {
async index({view})
{
const datanmea = await NmeaModel.all()
const jsondata = datanmea.toJSON()
var promises = [];
var nmeadata = "";
jsondata.forEach(element => {
promises.push(
new Promise(resolve => {
let x = element.nmea
let buff = new Buffer(x, 'base64')
zlib.unzip(buff,
(err, res) => {
if (err) {
// since you are interested in the text only, so no need to reject here
return resolve("");
}
return resolve("greed island")
})
}
)
)
});
const result = await Promise.all(promises); // this will be an array of results of each promises respectively.
nmeadata = result.join(""); // process the array of result
return view.render('admin.index', {
data: datanmea.toJSON(),
nmea: nmeadata
});
}
}
I'd suggest two things-
modify zlib.unzip callback function to resolve properly;
(err, res) => {
if (err) {
// since you are interested in the text only, so no need to reject here
return resolve("");
}
return resolve(res.toString())
}
retrieve the final data from the result of Promise.all
const result = await Promise.all(promises); // this will be an array of results of each promises respectively.
nmeadata = result.join(""); // process the array of result
In this approach every promise will resolve and finally you will get the expected result in array.

How do I run multiple node streams in sequence?

Given two streams, stream1, stream2, how can I run them in sequence, throwing if any fails?
I'm looking for something simpler than this:
stream1.on('end',function(){
stream2.on('end',done);
});
stream1.on('error',function(error){done(error);});
stream2.on('error',function(error){done(error);});
Thanks.
there are some ways to do that, check next link, it gonna help to how to write some code in node in a elegant way:
Node.js FLOW
Hope it what you need.
Depending on your use case you could combine the two streams into one by using the multistream module.
Multistreams are constructed from an array of streams
var MultiStream = require('multistream')
var fs = require('fs')
var streams = [
fs.createReadStream(__dirname + '/numbers/1.txt'), // contains a single char '1'
fs.createReadStream(__dirname + '/numbers/2.txt'), // contains a single char '2'
fs.createReadStream(__dirname + '/numbers/3.txt') // contains a single char '3'
]
MultiStream(streams).pipe(process.stdout) // => 123
In case combining streams does not fit the use case you could build your stream on end event sending functionality on your own
const fs = require('fs');
var number1 = fs.createReadStream('./numbers1.txt')
.on('data', d => console.log(d.toString()));
var number2 = fs.createReadStream('./numbers2.txt')
.on('data', d => console.log(d.toString()));
onEnd([number1, number2], function(err) {
console.log('Ended with', err);
});
function onEnd(streams, cb) {
var count = streams.length;
var ended = 0;
var errored = null;
function shouldEnd() {
ended++;
if (errored) { return; }
if (count == ended) {
cb();
}
}
function endWithError(err) {
if (errored) { return; }
errored = true;
cb(err);
}
streams.forEach(s => s
.on('end', shouldEnd)
.on('error', endWithError)
);
}
The onEnd function can be used to wait for a stream array to end or in case an error event is emitted for the first emitted error event.
Try do it with async functions:
const { createReadStream } = require("fs")
async function main() {
const stream1 = createReadStream(__dirname + "/1.txt")
await pipe(stream1, process.stdout)
const stream2 = createReadStream(__dirname + "/2.txt")
await pipe(stream2, process.stdout)
const stream3 = createReadStream(__dirname + "/3.txt")
await pipe(stream3, process.stdout)
}
async function pipe(tap, sink) {
return new Promise((resolve, reject) => {
tap.pipe(sink, { end: false })
tap.on("end", resolve)
tap.on("error", reject)
})
}
Try do it with Promise
function doStream1(cb) {
// put those operation on stream1 in the callback function...
cb && cb();
var p = new Promise(function(resolve, reject) {
stream1.on( 'end', resolve );
stream1.on( 'error', reject );
});
return p;
}
function doStream2(cb) {
// some operation on stream2 on callback
cb && cb();
var p = new Promise(function(resolve, reject) {
stream2.on( 'end', resolve );
stream2.on( 'error', reject );
});
return p;
}
doStream1(cb).then(function() {
return doStream2(cb);
}).catch(function(e) {
// error handling is here
});
Try the code below(sequenceStream function). The thing which I am worried about is error handling, but it should work.
Also, if you use Node < 10.* then you need end-of-stream instead of stream.finished
const stream = require('stream');
const process = require('process')
const { promisify } = require('util')
const fromList = (lst) => new stream.Readable({
read() {
if (lst.length) {
this.push(String(lst.shift()))
} else {
this.push(null)
}
}
})
const _finished = promisify(stream.finished)
const sequenceStream = (streams) => {
const resultingStream = new stream.PassThrough()
let isNext = Promise.resolve()
for(const [i, curStream] of streams.entries()) {
isNext = isNext.then(() => {
curStream.pipe(resultingStream, {end: i === streams.length -1})
return _finished(curStream)
}).catch((err) => {
resultingStream.write(err)
})
}
return resultingStream
}
sequenceStream([
fromList([1, 2, 3, 4]),
fromList([5, 6, 7, 8]),
fromList([9, 10])
]).pipe(process.stdout)

Resources