5

I would like to use the Fetch API in a browser extension to download a resource and compute a hash thereof. The following works (using crypto through Browserify)

fetch(url).then(function(response) {
  return response.blob();
}).then(function(data) {
  var a = new FileReader();
  a.readAsBinaryString(data);
  a.onloadend = function() {
    var hash = crypto.createHash(hashType);
    hash.update(a.result, 'binary');
    return hash.digest('hex');
  };
})

but has the disadvantage that I have to wait for a.onloadend while the context in which I'd like to embed it requires a Promise to be returned. Also, it seems quite weird to first fetch the entire blob, then read it into a FileReader just to dump it into createHash afterwards.

Any hints?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • Maybe this question will better fit for http://codereview.stackexchange.com? – Pavlo Nov 25 '15 at 21:16
  • This question looks like it might be a pretty good fit for [Code Review.SE](http://codereview.stackexchange.com/), provided that (a) you want _every aspect_ of your code reviewed, not just some, (b) your code is _already working_, and (c) you're asking for a review of _concrete, real code_, not abstract design (whether or not it's expressed as code). If you agree with all of those, please read about [what's on topic](http://codereview.stackexchange.com/help/on-topic), and, if your question fits that, delete it here and repost it on CR. – Phrancis Nov 25 '15 at 21:18
  • (a) and (c) are not applicable here, so I guess it's not a fit. – Nico Schlömer Nov 25 '15 at 21:20
  • 1
    @NicoSchlömer OK thanks for clarifying, in that case Stack Overflow should work out just fine! – Phrancis Nov 25 '15 at 21:25
  • 1
    I can't tell if this is for node or a web browser? Fetch API seems to hint web browser and `crypto.createHash` seems to hint node (Chrome does not appear to have that in `crypto`)...but regardless, if this is working like you say then it seems like you can just take `data` directly from the resolved promise and use it in `hash.update` or does that blow up? – Jack Nov 25 '15 at 21:40
  • This is in a browser extension, `require('crypto')` is used from browserify. Dumping `data` into `hash.update()` directly gives the wrong hash value. – Nico Schlömer Nov 25 '15 at 21:45
  • Ah, okay. That makes sense then..would have been nice to have in the original question. Have you tried `return response.arrayBuffer();` instead of `return response.blob();` and then trying `hash.update` on that's `data` instead (without the `input-type`)? The node docs imply that would work, but I don't know if what you're using is somehow different. – Jack Nov 25 '15 at 22:12
  • It makes no difference if I use `arrayBuffer` or `blob`. The computed hash for both is equal, and equally wrong. – Nico Schlömer Nov 25 '15 at 22:37
  • @Jack: I think the node docs mean a [node `Buffer`](https://nodejs.org/api/buffer.html), not a (typed array) `ArrayBuffer`. It would be interesting whether browserify-crypto does this differently maybe, however unfortunately I could not find any docs for them. – Bergi Nov 26 '15 at 08:11

2 Answers2

2

I think what you're asking for here is promise chaining. You can create a promise inside the then handler and return it.

var yaypromise = fetch(url).then(function(response) {
  return response.blob();
}).then(function(data) {
  return new Promise(function(resolve, reject){
      var a = new FileReader();
      a.readAsBinaryString(data);
      a.onloadend = function() {
        var hash = crypto.createHash(hashType);
        hash.update(a.result, 'binary');
        resolve(hash.digest('hex'));
      };  
  });
})

And then yaypromise is probably the promise you're looking for. It will resolve with hash.digest('hex')

david
  • 17,925
  • 4
  • 43
  • 57
  • Please promisify at the lowest level only. Put the call to `crypto` in a promise callback. And don't forget to attach an error handler. – Bergi Nov 26 '15 at 05:18
2

The crypto hash.update method also takes a buffer, so there is no need to make a detour via a FileReader. Just do

fetch(url).then(function(response) {
    return response.arrayBuffer();
}).then(function(arrayBuffer) {
    var buffer = require('buffer')(new Uint8Array(arrayBuffer));
    var hash = require('crypto').createHash(hashType);
    hash.update(buffer, 'binary');
    return hash.digest('hex');
})

If that doesn't work, you can easily promisify a FileReader:

function getResult(reader) {
    return new Promise(function(resolve, reject) {
        reader.onload = function() {
            resolve(this.result);
        };
        reader.onerror = reader.onabort = reject;
    });
}

and use it like this:

fetch(url).then(function(response) {
    return response.blob();
}).then(function(data) {
    var a = new FileReader();
    a.readAsBinaryString(data);
    return getResult(a);
}).then(function(result) {
    var hash = crypto.createHash(hashType);
    hash.update(result, 'binary');
    return hash.digest('hex');
})
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • In your first code block why is the synchronous code in the second `.then()` handler even in a second `.then()` handler? Why isn't it just in the first `.then()` handler with no second `.then()` handler? – jfriend00 Nov 26 '15 at 08:05
  • @jfriend00: `arrayBuffer()` returns a promise – Bergi Nov 26 '15 at 08:06