Skip to content

Instantly share code, notes, and snippets.

@iansinnott
Created June 5, 2017 19:36
Show Gist options
  • Save iansinnott/3d0ba1e9edc3e6967bc51da7020926b0 to your computer and use it in GitHub Desktop.
Save iansinnott/3d0ba1e9edc3e6967bc51da7020926b0 to your computer and use it in GitHub Desktop.
A simple Rx abstraction over the FileReader API
/**
* Read the text contents of a File or Blob using the FileReader interface.
* This is an async interface so it makes sense to handle it with Rx.
* @param {blob} File | Blob
* @return Observable<string>
*/
const readFile = (blob) => Observable.create(obs => {
if (!(blob instanceof Blob)) {
obs.error(new Error('`blob` must be an instance of File or Blob.'));
return;
}
const reader = new FileReader();
reader.onerror = err => obs.error(err);
reader.onabort = err => obs.error(err);
reader.onload = () => obs.next(reader.result);
reader.onloadend = () => obs.complete();
return reader.readAsText(blob);
});
@iansinnott
Copy link
Author

If you wanted to mock out the file reader interface I would suggest simply passing in a constructor as a second argument with a default so that you can ignore the second argument outside of testing.

const readFile = (blob, Reader = FileReader) => { /* ... */ }

@jeserkin
Copy link

Sounds interesting. Will try it out. Thanks.

@jakehockey10
Copy link

If you were using Jasmine, could you use spyOn to mock FileReader?

@iansinnott
Copy link
Author

Not sure, but using the approach above you could pass in anything you want in place of file reader so I think it would solve this use case. Here's an updated example using typescript and the new Observable syntax from Rxjs 7.

const readFile = (blob: Blob, reader: FileReader = new FileReader()) => new Observable(obs => {
  if (!(blob instanceof Blob)) {
    obs.error(new Error('`blob` must be an instance of File or Blob.'));
    return;
  }

  reader.onerror = err => obs.error(err);
  reader.onabort = err => obs.error(err);
  reader.onload = () => obs.next(reader.result);
  reader.onloadend = () => obs.complete();

  return reader.readAsText(blob);
});

Then in your test code something like this. Not sure what you would want here but that would depend on your test suite.

class MockReader { /* ... */ }
const reader = new MockReader();
await readFile(someBlob, reader).toPromise();
expect(reader.readAsText).toHaveBeenCalled();

@OliverJAsh
Copy link

I think this should also call reader.abort() when the Observable is unsubscribed from.

@MuazSamli
Copy link

here is a simple implementation of opening an image in browser using RxJS 7.1

<input type='file' id='file-input'>
<br>
<img src="" id='image-preview' width='200px'>

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.1.0/rxjs.umd.js"></script>
<script>
  const { fromEvent, Observable } = rxjs;
  const { flatMap } = rxjs.operators;
  
  var fileSelectStream = fromEvent(document.getElementById('file-input'), 'change');
  
  function createRxObservable(fileNameEvent){
    var file = fileNameEvent.target.files[0];
  
    const readFile_rx_observable = Observable.create(
      function(subscriber) {
        var reader = new FileReader();
        reader.onload = function(e) {
          file.content = e.target.result;
          subscriber.next(file);
        };
        reader.readAsDataURL(file);
      }
    );
    return readFile_rx_observable;
  }
  
  var fileReadStream = fileSelectStream
     .pipe(flatMap(createRxObservable));
  
  fileReadStream.subscribe((file)=>{
    document.getElementById('image-preview').src = file.content;
  });
</script>

a working fiddle is here: https://jsfiddle.net/MuazSamli/5urd9bcq/25/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment