Return a promise result in order using the library

2019-07-27 09:01发布

问题:

Using the dom-to-image library we can convert an image using Promises.

Usage example, conversion from DOM object to PNG image:

var node = document.getElementById('my-node');

domtoimage.toPng(node)
    .then(function (dataUrl) {
        var img = new Image();
        img.src = dataUrl;
        document.body.appendChild(img);
    })
    .catch(function (error) {
        console.error('oops, something went wrong!', error);
    });

Behind the scenes, the toPng method is chain of a Promise object:

/**
 * @param {Node} node - The DOM Node object to render
 * @param {Object} options - Rendering options, @see {@link toSvg}
 * @return {Promise} - A promise that is fulfilled with a PNG image data URL
 * */
function toPng(node, options) {
    return draw(node, options || {})
        .then(function (canvas) {
            return canvas.toDataURL();
        });
}

I want to convert images based on a Node array, and then, to get that images in order. The problem is when I call toPng() the async process time varies in function of the DOM target complexity. So it will return before the pages with less complexity, instead of return the pages in order.

let pages = document.querySelectorAll('.page');

pages.forEach(page => {
  let dataUrl = convertToPng(page.firstElementChild);
});

function convertToPng(page) {
  domtoimage.toPng(page)
    .then(function (dataUrl) {
      console.log(page); // prints pages randomly (e.g. page3, page1, page2) 
    })
}

A possible solution would be use a Promise.all() method and attach with a forEach() method all the Promises. But in this case, that I'm using an external library, I don't know how to approach in order to get first the page1, then the page 2, and so on...

回答1:

As I understand for this case you can use Promise.all. Smthing like this one:

let pages = document.querySelectorAll('.page');

Promise.all(Array.prototype.slice.call(pages,0)
.map(page =>domtoimage.toPng(page)
).then((pages)=>{.... your handler....});

More information about Promise.all you can get from MDN



回答2:

Using async/await:

HTML:

<div class="page">Hello 1</div>
<div class="page">Hello 2</div>
<div class="page">Hello 3</div>
<div class="page">Hello 4</div>

JS (JSFiddle Link):

let pages = document.querySelectorAll('.page');

async function getPageUrls() {
    for (let page of pages) {
        const url = await convertToPng(page);
        console.log(url);
    }
}

async function convertToPng(page) {
    return await domtoimage.toPng(page);
}

getPageUrls();

Based off OPs sample (JSFiddle):

let pages = document.querySelectorAll('.page');

for (let page of pages) {
    convertToPng(page);
}

async function convertToPng(page) {
    const dataUrl = await domtoimage.toPng(page);
    console.log(dataUrl);
}

getPageUrls();

With this solution code execution is stopped until convertToPng yields a result at each iteration.



回答3:

The easiest way to chain the promises so they run in sequence is using Array#reduce

pages.reduce((promise, page) => 
    promise.then(() => convertToPng(page.firstElementChild)),
    Promise.resolve() // this starts the chain
);

This will, as you stated in the comment wait until page1 is complete, before beginning with the next page

Though, I get the feeling it's only the output order that is important, in which case the answer by @VasylMoskalov is more appropriate

As @Keith pointed out in a comment - convertToPng does not return anything (let alone a promise)

function convertToPng(page) {
//vvvvvv important you return the promise here
  return domtoimage.toPng(page)
    .then(function (dataUrl) {
      console.log(page); // prints pages randomly (e.g. page3, page1, page2) 
    })
}