简体   繁体   中英

TypeScript Javascript Class, two instances this get mixed when returning Promise

I have a typescript app that it is mixing 'this' context inside a class.

This class is responsible to setup new Express server instance.

If I track 'this' with debugger and node --inspect-brk we can see 'this' is undefined when serverProtocol gets resolved but it does not matter !

I've setup a little project to show what i'm facing.

$> git clone https://github.com/AdSegura/ThisLoseContext.git

$> cd ThisLoseContext/

$> npm i

$> npm run build && node dist/

What output should expect:

INIT: Server_ID: quijote_1111, PORT: 1111
-----------------------------------------
INIT: Server_ID: quijote_2222, PORT: 2222
-----------------------------------------
Callback: Server_ID: quijote_1111, PORT: 1111 
ServerProtocol: Server_ID: quijote_1111; PORT: 1111
Callback: Server_ID: quijote_2222, PORT: 2222
ServerProtocol: Server_ID: quijote_2222; PORT: 2222

What is the real output:

INIT: Server_ID: quijote_1111, PORT: 1111
-----------------------------------------
INIT: Server_ID: quijote_2222, PORT: 2222
-----------------------------------------
Callback: Server_ID: quijote_1111, PORT: 2222 [ERROR]
ServerProtocol: Server_ID: quijote_1111; PORT: 2222 [ERROR]
Callback: Server_ID: quijote_2222, PORT: 2222
ServerProtocol: Server_ID: quijote_2222; PORT: 2222

The problem is in Server.ts this.serverProtocol() is mixing this.options.port

var http = require('http');
var express = require('express');
const os = require("os");

export class Server {
    /** The http server.*/
    public express: any;

    /** express httpServer */
    protected server: any;

    /** id representing server instance */
    protected server_id: any;

    /**
     * Create a new server instance.
     */
    constructor(private options: any) {
        this.server_id = this.generateServerId();
    }

    /**
     * Start the Socket.io server.
     *
     * @return {void}
     */
    init(): Promise<any> {
        console.log(`INIT: Server_ID: ${this.server_id}, PORT: ${this.options.port}`);
        return new Promise((resolve, reject) => {
            debugger;
            this.serverProtocol().then(instance => {
                debugger;
                console.log(`ServerProtocol: Server_ID: ${this.server_id}; PORT: ${this.options.port}`);
                resolve();
            }, error => reject(error));
        });
    }

    /**
     * Select the http protocol to run on.
     *
     * @return {Promise<any>}
     */
    serverProtocol(): Promise<any> {
        return this.httpServer()
    }

    /**
     * Express socket.io server.
     */
    httpServer(): Promise<any> {
        return new Promise((resolve, reject) => {

            this.express = express();
            this.express.use((req, res, next) => {
                for (var header in this.options.headers) {
                    res.setHeader(header, this.options.headers[header]);
                }
                next();
            });

            const httpServer = http.createServer(this.express);

            function cb() {
                debugger;
                console.log(`Callback: Server_ID: ${this.server_id}, PORT: ${this.options.port}`)
                return resolve.call(this, this)
            }

            this.server = httpServer.listen(this.options.port, this.options.host, () =>  cb.call(this));
        })
    }

    /**
     * Generate Server Id
     *
     * @return string hostname_port
     */
    generateServerId(): string {
        const hostname = os.hostname();
        const port = this.options.port;

        return `${hostname}_${port}`
    }

}

I'm a novice with typescript, I tried multiple targets at tsconfig but same result, this.options gets the last config {port:2222} when you instanciate two or more objects.

Thanks.

Update

I think understand this:

There is no such thing as a "local object". You do have a reference to an object. Two instances might have two references to the same object.

this.options point to global opt variable, BAD

const opt = { foo: 3 };

export class MyClass {
    constructor(private options: any){

        this.options = Object.assign(opt, options)
    }

    getOptions(): any {
        return this.options
    }
}

this.options point to local opt variable, GOOD


export class MyClass {
    constructor(private options: any){
        const opt = { foo: 3 };
        this.options = Object.assign(opt, options)
    }

    getOptions(): any {
        return this.options
    }
}

But as our partners said bellow, the safest methods to create new options variable are:

this.options = Object.assign({}, this.defaultOptions, config);

or

this.options = { ...this.defaultOptions, ...config };

And now we have a new object not a copy to an object outside the scope of the constructor method.

So the safest version of the code should be:


export class MyClass {
    constructor(private options: any){
        const opt = { foo: 3 };
        this.options = Object.assign({}, opt, options)
    }

    getOptions(): any {
        return this.options
    }
}

The issue is probably inside the EchoServer class, namely here:

 this.options = Object.assign(this.defaultOptions, config);

Thats equal to:

 this.options = this.defaultOptions;
 Object.assign(this.defaultOptions, config);

I assume you actually wanted to do:

 this.options = Object.assign({}, this.defaultOptions, config);

which creates an own options object for each instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM