0

We have two typescript class. On of them define a method as an object argument then transmitted in another class.

class MyService {
    public options: { [key: string]: any };

    constructor() { }

    public upload(file: File): void {
        const parallelHasher = new ParallelHasher('/public/md5_worker.js');

        this.options = {
            concurrency: 1,
            autoUpload: true,
            hashMethod: parallelHasher.hash // HERE
        };

        const uploadService = new UploadService(this.options);
        this.uploadsProgress = uploadService.start(file)
    }
}


class UploadService {
    constructor(public options: any) { }

    public async start(file: File): Promise<void> {
        const hash = await this.options.hashMethod(file);

        this.proceed(file, hash);
    }

    public proceed(file: File, hash: string): void {
        // do something
    }
}

We encounter an error when UploadService.start() is called due to the hash method of the third party:

ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'push' of undefined
TypeError: Cannot read property 'push' of undefined
    at parallel_hasher.js:25

Here is the relevant part in third party code:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ParallelHasher = (function () {
    function ParallelHasher(workerUri) {
        this._queue = [];
        this._ready = true;
        var self = this;
        if (Worker) {
            self._hashWorker = new Worker(workerUri);
            self._hashWorker.onmessage = self._recievedMessage.bind(self);
            self._hashWorker.onerror = function (err) {
                self._ready = false;
                console.error('Hash worker failure', err);
            };
        }
        else {
            self._ready = false;
            console.error('Web Workers are not supported in this browser');
        }
    }
    ParallelHasher.prototype.hash = function (blob) {
        var self = this;
        var promise;
        promise = new Promise(function (resolve, reject) {
            self._queue.push({
                blob: blob,
                resolve: resolve,
                reject: reject,
            });
            self._processNext();
        });
        return promise;
    };
    return ParallelHasher;
}());
exports.ParallelHasher = ParallelHasher;

As you can see _queue is defined in the constructor and should be there when the hash() method tries to use it to push new element to queue.

By placing a debugger on the line with var self = this; in the third party's hash method, the value of this is equivalent to this.options in MyService class, instead of being ParallelHasher:

concurrency: 1,
autoUpload: true,
hashMethod: parallelHasher.hash

And indeed, there is no _queue in options, hence the error.

Somehow, the context of the third party's method call is set, not in the ParallelHasher instance but in the options object.

I don't really understand why, and how to prevent this from occuring.

BlackHoleGalaxy
  • 7,518
  • 13
  • 46
  • 88
  • 1
    See how the code uses `.bind()` in the hasher for the `_receivedMessage` function? That's what you have to do. JavaScript methods have no intrinsic binding to objects unless you force them to. – Pointy Apr 23 '19 at 00:08
  • 2
    So `= parallelHasher.hash.bind(parallelHasher)` – Pointy Apr 23 '19 at 00:09
  • Yes it works. Just to understand for next time, calling the hash just after defining the class instance: `const parallelHasher = new ParallelHasher('/public/md5_worker.js'); parallelHasher.hash(file);` works and it then doesn't wait context. Why does it loose binding context after having been passed inside the options object? – BlackHoleGalaxy Apr 23 '19 at 00:17
  • It doesn't *lose* binding context: there **is no binding context**. JavaScript doesn't work that way. The value of `this` in a function is determined by how a function is called (or, if it's been created by `.bind()`, by the parameters to that function). – Pointy Apr 23 '19 at 03:06
  • 1
    [See this older question for more information.](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Pointy Apr 23 '19 at 03:07

1 Answers1

2

The library's parallelHasher.hash method isn't bound to this, so when you pass the method itself, it loses its this pointer. Instead, in your options, you can create an anonymous function, so that the library method keeps its this context and you don't have to modify the library itself.

        this.options = {
            concurrency: 1,
            autoUpload: true,
            hashMethod: (blob) => {
                return parallelHasher.hash(blob);
            }
        };

Here's some docs that are worth reading to better understand what's going on: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

frodo2975
  • 6,794
  • 1
  • 24
  • 34