creating asynchronous function with a loop inside - javascript

I have a little problem, I need the makeZip function to wait for the takeScreenshot function to take all the screenshots it needs, how do I do this while taking care of best practices?
(I know at this point "then" doesn't make sense with the post method, I just tried it my way before but it didn't work the way I wanted)
Function:
const takeScreenshot = (url) => {
const resolutionsArray = Object.values(resolutions);
resolutionsArray.map(async (mediaSize) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (!err) {
console.log("screenshot taken!");
}
});
});
};
calling functions:
app.post("/", async (req, res) => {
const { url } = req.body;
takeScreenshot(url)
.then((url) => makeZip(url))
.then((url) => sendEmail(url))
.then((message) => res.send(message))
.catch((err) => console.log(err));
});

My suggestion is:
to use Promise.all or Promise.allSettled when you need to handle several promises
extract callback of map fn
const makeWebshot = (argsHere) => new Promise((reselove, reject) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (err) return reject(err);
return resolve();
});
});
Update takeScreenshot to
const takeScreenshot = (url) => {
const resolutionsArray = Object.values(resolutions);
return Promise.all(resolutionsArray.map((mediaSize) => makeWebshot(argsHere)));
};

When dealing with a list of Promises you will want to use Promise.all to wait for them all to resolve. Here is a simple example:
const list = [1,2,3];
const all = list.map(i => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve(i*2);
}, 100);
}));
Promise.all(all).then(console.log)
In your case it would be something like this:
const takeScreenshot = (url) =>
Object.values(resolutions).map(async (mediaSize) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (!err) {
console.log("screenshot taken!");
}
});
});
app.post("/", async (req, res) => {
const { url } = req.body;
Promise.all(takeScreenshot(url))
.then((listOfUrls) => ...
});
But since I don't know what webshot returns, I can't tell you what the processing of the listOfUrls should look like.

Related

Async simpleParser return empty array

I want to get all my email in a list but my promises array is empty. I think it related to some asynchronous problems but I can't figure out what's the problem.
var promises = [];
const imap = new Imap(imapConfig);
imap.once("ready", () => {
imap.openBox("INBOX", false, () => {
imap.search(["ALL", ["SINCE", new Date()]], (err, results) => {
const f = imap.fetch(results, { bodies: "" });
f.on("message", (msg) => {
msg.on("body", (stream) => {
simpleParser(stream, async (err, parsed) => {
const { from, subject, textAsHtml, text } = parsed;
promises.push(text);
});
});
});
f.once("end", () => {
console.log("Done fetching all messages!", promises);
imap.end();
});
});
});
});
imap.connect();
return await Promise.all(promises);
The issue is arising due to mixing callbacks with promises. A callback is not synchronous. The way your code works as of now is:
var promises = [];
const imap = new Imap(imapConfig);
imap.once("ready", () => {/*async code*/});
return await Promise.all(promises);
For the simplest solution, you need to wrap the logic inside a new Promise object.
Using this, the solution would be:
let promise = new Promise((resolve, reject) => {
var promises = [];
const imap = new Imap(imapConfig);
imap.once("ready", () => {
imap.openBox("INBOX", false, () => {
imap.search(["ALL", ["SINCE", new Date()]], (err, results) => {
const f = imap.fetch(results, { bodies: "" });
f.on("message", (msg) => {
msg.on("body", (stream) => {
simpleParser(stream, async (err, parsed) => {
const { from, subject, textAsHtml, text } = parsed;
promises.push(text);
});
});
});
f.once("end", () => {
console.log("Done fetching all messages!", promises);
imap.end();
resolve(promises);
});
});
});
});
imap.connect();
});
let promises = await promise;
return await Promise.all(promises);
Other possible solution: promisify callbacks https://zellwk.com/blog/converting-callbacks-to-promises/

promise.all to use getDownloadURL() in firebase

I'm struggling to fetch the download image URL in firebase storage.
What I wanted to achieve with this code is to upload two images one after another and to push the image URL to the array.
However, my code doesn't work correctly as asynchronous.
Ther result after Promise.all() suppose to return the array with URL.
I'll appreciate if someone guides me to solve this issue.
const handleUpload = () => {
Promise.all([uploadImage(window1Image), uploadImage(window2Image)])
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
const uploadImage = (image) => {
const uuid = Date.now() + uuidv4();
const imageToServer = storage.ref(`images/${uuid}`).put(image);
imageToServer.on(
'state_changed',
(snapshot) => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(uuid)
.getDownloadURL()
.then((data) => data);
},
);
};
You should use the then() method of UploadTask which "behaves like a Promise, and resolves with its snapshot data when the upload completes".
The following should work (untested):
const handleUpload = () => {
Promise.all([uploadImage(window1Image), uploadImage(window2Image)])
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
});
};
const uploadImage = image => {
const uuid = Date.now() + uuidv4();
const imageToServer = storage.ref(`images/${uuid}`).put(image);
return imageToServer.then(uploadTaskSnapshot => {
return uploadTaskSnapshot.ref.getDownloadURL();
});
};

is it normal to pass callback into a async function and even wrap it again?

app.js
import test from "./asyncTest";
test().then((result)=>{
//handle my result
});
asyncTest.js
const test = async cb => {
let data = await otherPromise();
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then( => response.json())
.then(json => json );
}, 2000);
};
export default test;
The fetch result "json" I intend to return is unable to be the return value of "test" function since the value only available in an inner function scope such as debounce wrapper. Since above reason, I tried to pass a callback function and wrap the callback to be Promise function(pTest) as below.
const test = async cb => {
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => cb(null, json))
.catch(err => cb(err));
}, 2000);
};
const pTest = cb => {
return new Promise((resolve, reject) => {
test((err, data) => {
if (err) reject(err);
resolve(data);
});
});
};
export default pTest;
This way works for me, but I'm wondering if it's correct or are there any ways to solve this scenario?
The fetch API already returns a promise. Wrapping it in another Promise object is actually an anti-pattern. it is as simple as the code below:
/*export*/ async function test() {
let data = await otherPromise();
return fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => {
return {
json: json,
data: data
}
});
};
function otherPromise() {
return new Promise((resolve, reject) => {
resolve('test for data value');
});
}
// In index.js call
test().then(res => {
console.log(res)
});

How should I download a file in Node? [duplicate]

I have this code that serves every markdown file in the './markdown' folder. At '/api/markdown/filename'.
var apiRouter = express.Router();
markdownFolder = './markdown/';
apiRouter.get('/:markdown_file_noext', function(req, res) {
fs.readdir(markdownFolder, function(err, markdown) {
if (err) throw err;
markdown.forEach(function(file) {
fs.readFile(markdownFolder + file, 'utf8', function(err, file_content) {
if (err) throw err;
fileNoExtension = file.slice(0, file.indexOf('.'));
if (req.params.markdown_file_noext == fileNoExtension) {
res.json({
'title': fileNoExtension,
'markdown': marked(file_content)
});
};
});
});
});
});
But i end having a ton of callbacks do the the nature of the 'fs' methods. How do i avoid this?
Using Q as promise library:
const Q = require('q');
const fs = require('fs');
const markdownFolder = './markdown/';
const readdir = Q.nfbind(fs.readdir);
const readFile = Q.nfbind(fs.readFile);
readdir(markdownFolder).then(markdown => {
const promises = [];
markdown.forEach(file => promises.push(readFile(markdownFolder + file, 'utf8')));
return Q.all(promises);
}).then(files => {
// Do your magic.
}).catch(error => {
// Do something with error.
});
You have different option.
Use named Function instead of anonymus functinos. It would make it a little bit more readable but you will still be using callbacks.
Use Promises, but you will need to use bluebird to wrap the fs module.
For a more advance option, you can use generators and Promises to make your code look more like a sync way. Take a look at co or bluebird.coroutine.
With Promises you could do like this:
const path = require('path');
var apiRouter = express.Router();
markdownFolder = './markdown/';
apiRouter.get('/:markdown_file_noext', function(req, res) {
readdir(markdownFolder)
.then((files) => {
const tasks = files.map((file) => {
const filePath = path.resolve(markdownFolder, file);
return readFile(filePath);
});
return Promise.all(tasks); // Read all files
})
.then((fileContents) => {
return fileContents.map((content) => {
fileNoExtension = file.slice(0, file.indexOf('.'));
if (req.params.markdown_file_noext == fileNoExtension) {
return {
'title': fileNoExtension,
'markdown': marked(content)
};
};
})
})
.then((results) => {
// It's better if you aggregate all results in one array and return it,
// instead of calling res.json for each result
res.json(results);
})
.catch((err) => {
// All errors are catched here
console.log(err);
})
});
function readdir(folderPath) {
return new Promise((resolve, reject) => {
fs.readdir(folderPath, (err, files) {
if (err) {
return reject(err);
}
resolve(files);
});
});
}
function readFile(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, file_content) => {
if (err) {
return reject(err);
}
resolve(file_content);
});
});
}

Converting callbacks with for loop and recursion to promises

I wrote a function running recursively to find out files whose name include given world. I do not understand how promises works and cannot find a way to write this function with promises despite trying hard.
I tried returning a promise inside findPath function but I couldn't use it since extractFiles calls findPath. I tried to create a list of promises and return all but couldn't succeed neither.
So how could I write these functions with promises?
const fs = require('fs');
const path = require('path');
function findPath(targetPath, targetWord, done) {
if (!fs.existsSync(targetPath)) return;
fs.readdir(targetPath, (err, allPaths) => {
if (err) done(err, null);
for (aPath of allPaths) {
aPath = path.join(targetPath, aPath);
extractFiles(aPath, targetWord, done);
}
});
function extractFiles(aPath, targetWord, done) {
fs.lstat(aPath, (err, stat) => {
if (err) done(err, null);
if (stat.isDirectory()) {
findPath(aPath, targetWord, done);
}
else if (aPath.indexOf(targetWord) >= 0) {
let fileName = aPath.split('.')[0];
done(null, fileName);
}
});
}
}
findPath('../modules', 'routes', file => {
console.log(file);
});
Firstly, to make the "core" code more readable, I'd promisify the fs functions
const promisify1p = fn => p1 => new Promise((resolve, reject) => {
fn(p1, (err, result) => {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
const readdirAsync = promisify1p(fs.readdir);
const lstatAsync = promisify1p(fs.lstat);
Then, just chain the promises as you would with any other promises
const fs = require('fs');
const path = require('path');
function findPath(targetPath, targetWord) {
const readPath = target =>
readdirAsync(target)
.then(allPaths =>
Promise.all(allPaths.map(aPath => extractFiles(path.join(target, aPath))))
.then(x => x.filter(x=>x)) // remove all the "false" entries - i.e. that don't match targetWord
.then(x => [].concat.apply([], x)) // flatten the result
);
const extractFiles = aPath =>
lstatAsync(aPath).then(stat => {
if (stat.isDirectory()) {
return readPath(aPath);
} else if (aPath.includes(targetWord)) {
return aPath.split('.')[0];
}
return false;
});
return readPath(targetPath);
}
findPath('../modules', 'routes')
.then(results => {
// do things with the results - which is an array of files that contain the targetWord
})
.catch(err => console.error(err));
Not much to it at all.

Categories

Resources