3

I am trying to download a large number images from their url(s) and then creating a PDF file out of them in Node.js. I'm using the image-downloader module to download the images in a promise chain and then once all promises are resolved, using another module, images-to-pdf, to trigger the creation of the pdf.
The problem is that most of the promises get resolved immediately in around 5-6 seconds, but there are a few images that are taking quite a bit of time to download. I'd like to force trigger the PDF creation after a certain interval of waiting. Can that be done?
Here is the code

var promises = data.map(function(element){
    return download.image(element)
    .then( ({filename}) => {
        console.log("Saved to ",filename);
        return filename;
    })
});

Promise.all(promises).then(function (data){
    var fileName = url.slice(url.indexOf("files")+7, url.length-1).replace("/","_")+".pdf";
    data.forEach(
        (element) => {console.log(element);
    });
    imagesToPdf(data, fileName)
    .then(console.log(" PDF creation done."));
})
.catch ((error) => {
    console.error(error);
});

The data getting passed in data.map is a JSON object of the following nature -

[
  { 
    url:"https://path//to//image//001.jpg",
    dest:"local//path//to//image//001.jpg"
  },
  { 
    url:"https://path//to//image//002.jpg",
    dest:"local//path//to//image//002.jpg"
  },
  { 
    url:"https://path//to//image//003.jpg",
    dest:"local//path//to//image//003.jpg"
  },
  ...
}]
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
srdg
  • 585
  • 1
  • 4
  • 15
  • Please use callback. Then you can catch the moment when downloading image. After that, you can use imagetopdf.. –  Dec 27 '20 at 06:28
  • 1
    @ArtemMedianyk - There is no point to adding plain callbacks to code that is already using promises. – jfriend00 Dec 27 '20 at 06:30
  • Do not use promise. Just use data.map(element, i) => { something } –  Dec 27 '20 at 06:32
  • @ArtemMedianyk -- `download.image(element)` is asynchronous and returns a promise. You have to use something to do asynchronous error handling and to know when they are all done. This is what promises are built for and the core operation here already uses promises. It makes no sense to keep recommending that one doesn't use promises with a series of asynchronous operations that already use promises. – jfriend00 Dec 27 '20 at 07:57

1 Answers1

1

You can use Promise.race() to add a timeout to each images promise and if you're going to resolve it, then you need to resolve it with some sentinel value (I use null here in this example) that you can test for so you know to skip it when processing the results.

function timeout(t, element) {
    return new Promise(resolve => {
        // resolve this promise with null
        setTimeout(resolve, t, element);
    });
}

var promises = data.map(function(element) {
    const maxTimeToWait = 6000;
    return Promise.race([download.image(element).then(({ filename }) => {
        console.log("Saved to ", filename);
        return filename;
    }), timeout(maxTimeToWait)]);
});

Then, in your code that processes the results from Promise.all(), you need to check for null and skip those because those are the ones that timed out.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Is there a way for me to resolve the promise with whatever content is associated with the particular image till then? – srdg Dec 27 '20 at 06:28
  • @srdg - Just add an argument to the `timeout()` function that is your constant and pass that instead of where you see `null` in the timeout function. That will resolve the promise with that value then. – jfriend00 Dec 27 '20 at 06:29
  • Okay, so I know I need to pass this argument. How do I access the content? Can I do `timeout(maxTimeToWait, element)`? – srdg Dec 27 '20 at 06:31
  • More efficient to have just one timeout Promise and race all the downloads against it. – Roamer-1888 Dec 27 '20 at 06:36
  • @Roamer-1888 yes, but the nature of the images is such that each takes a different time to resolve. I fear they might not get resolved in a common race. Just out of curiosity, how do I make the race common? Could you please advise? – srdg Dec 27 '20 at 06:40
  • No, not a "common race", rather individual races against the same timeout. It's essentially the same as jfr.. has given you but more efficient. – Roamer-1888 Dec 27 '20 at 06:45
  • @srdg - The function you're using to get your images is all or nothing. As you are using it, it does not provide any partial results. I'm not sure what use a partially downloaded image would be anyway. But, if you wanted such a thing, you'd have to download differently using some sort of stream interface so you can collect the results as they arrive. – jfriend00 Dec 27 '20 at 07:50
  • @Roamer-1888 - Timers are super efficient in nodejs. There is no need to add any code complication to save a couple of timers. If you have a materially better way to do this, then please post your own answer. – jfriend00 Dec 27 '20 at 07:51
  • @srdg - What is an `element`? – jfriend00 Dec 27 '20 at 07:53
  • @jfriend00 as I have understood, `element` refers to each element in the JSON object. – srdg Dec 27 '20 at 08:03
  • @srdg - And that's what you want the result to be if it times out? – jfriend00 Dec 27 '20 at 08:04
  • @jfriend00 the `element` object contains an `url` and a `dest` - the image destination. I want the result to be whatever data is present in the path `dest` when the result times out. I tried a couple of variations by using `fs.readFileSync()` but it didn't work out. – srdg Dec 27 '20 at 08:06
  • @srdg - OK, I modified my answer to resolve to `element` if the download times out. Your code for processing results will have to distinguish between a filename from a successful download and an `element` object from one that timed out. You could probably do that by just checking `if (typeof result === "string")` – jfriend00 Dec 27 '20 at 08:51