Created
June 5, 2017 19:36
-
-
Save iansinnott/3d0ba1e9edc3e6967bc51da7020926b0 to your computer and use it in GitHub Desktop.
A simple Rx abstraction over the FileReader API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
}); |
Sounds interesting. Will try it out. Thanks.
If you were using Jasmine, could you use spyOn
to mock FileReader
?
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();
I think this should also call reader.abort()
when the Observable is unsubscribed from.
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
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.