Extracting pages from a PDF document and saving them as separate image files, JavaScript edition with Promises


Last time, we converted the C# version of the PDF Document sample program so that it saved the pages to disk as image files. Today we'll port those changes to JavaScript with Promises.

function viewPage() {
  WinJS.log && WinJS.log("", "sample", "status");

  var pageNumber = parseInt(pageNumberBox.value, 10);
  if (isNaN(pageNumber) || (pageNumber < 1) ||
    (pageNumber > pdfDocument.pageCount)) {
    WinJS.log && WinJS.log("Invalid page number.", "sample", "error");
    return;
  }

  output.src = "";
  progressControl.style.display = "block";

  // Convert from 1-based page number to 0-based page index.
  var pageIndex = pageNumber - 1;

  var page = pdfDocument.getPage(pageIndex);

  var picker = new Windows.Storage.Pickers.FileSavePicker();
  picker.fileTypeChoices["PNG image"] = [".png"];
  picker.pickSaveFileAsync().then(outfile => {
    if (outfile) {
      outfile.openTransactedWriteAsync().then(transaction => {
        var options = new PdfPageRenderOptions();
        options.destinationHeight = page.size.height * 2;
        options.destinationWidth = page.size.width * 2;
        page.renderToStreamAsync(transaction.stream, options).then(() => {
          transaction.close();
        });
      });
    }
  }).done(() => {
    page.close();
    // Delete the code that sets the blob into the image
    progressControl.style.display = "none";
  });
}

This is an uninspired direct translation of the C# code to JavaScript. We can imbue it with a little JavaScript inspiration by flattening the promise chain a bit.

  var transaction;
  var picker = new Windows.Storage.Pickers.FileSavePicker();
  picker.fileTypeChoices["PNG image"] = [".png"];
  picker.pickSaveFileAsync().then(outfile => {
    if (outfile) {
      return outfile.openTransactedWriteAsync();
    }
  }).then(trans => {
    transaction = trans;
    if (transaction) {
        var options = new PdfPageRenderOptions();
        options.destinationHeight = page.size.height * 2;
        options.destinationWidth = page.size.width * 2;
        return page.renderToStreamAsync(transaction.stream, options);
    }
  }).then(() => {
    transaction && transaction.close();
  }).done(() => {
    page.close();
    // Delete the code that sets the blob into the image
    progressControl.style.display = "none";
  });

Instead of nesting the promises, I chained them, and each step of the chain checks whether the previous step succeeded before proceeding. (If not, then that step does nothing.)

Alternatively, I could've thrown the Promise into an error state, but WinRT tries to reserve exceptions for unrecoverable errors, primarily out-of-memory conditions for a small allocation, or a programmer error. Errors that a program is expected to recover from are generally reported by an in-API mechanism. (There are notable exceptions to this principle, primarily in the I/O area.)

Anyway, you may have noticed that I used arrow functions, which are feature of ES6. Next time, I'm going to take it even further.

Comments (10)
  1. kantos says:

    I would really hope that WinJS would support async and await which would make this a lot more readable, but I’m not sure if it tracks the same version of chakra that Edge uses.

    1. Ian Yates says:

      You could transpire with typescript?

    2. Neil says:

      The async-less version of chained Promises are particularly annoying due to the awkward way of storing intermediate results.

      I’m not sure why Raymond used `transaction && transaction.close()` but not `outfile && outfile.openTransactedWriteAsync()` (either with `return` or using implicit expression return).

      1. The “transaction &&” was introduced as part of the chain flattening. The “if (outfile)” was part of the original code. If I had changed the “if (outfile)” then that would’ve confused the issue, since it was not related to the stated transformation.

    3. contextfree says:

      WinJS is a JavaScript library, it’s not a general term for the WinRT support for JavaScript

  2. Someone says:

    Where / how could this JavaScript script be executed, if not in a browser?

    1. Dmitry Onoshko says:

      2 Someone
      Not sure I understood your question correctly, but from what I can tell, those UWP applications are yet another terribly (un?)documented piece of ZIPped manifests plus a tiny amount of useful stuff which can optionally be written in JavaScript and can call some APIs by (usually) using the WinJS library.

      I gave up trying to build such a thing manually quite soon, since the documentation was contradicting itself and split into multiple pieces. (I didn’t want to use the multi-gigabyte Visual Studio just to write a tiny useful app of a few kilobytes.)

      1. Someone says:

        I didn’t get where one could execute this code. (For example, I would expect that JavaScript in a browser is sandboxed so this code snippet would not be able to save files at some custom folder at all.)

        >>those UWP applications<<

        Applications in JavaScript sounds very strange to me.

  3. Osxpert says:

    The first JS example is a lot more readable than async await (dont like them)

    1. Voo says:

      New things take a bit to get used to, but async/await are great tools to simplify asynchronous code. The main advantages:
      – the logic is kept linear instead of being wrapped in callbacks which gets confusing very quickly when you are four levels nested
      – exception handling can be done in the standard way of the language instead of needing different callbacks

      Yes you’ll have to use some new technology, but it’s pretty simple, particularly for the standard use cases.

Comments are closed.

Skip to main content