Just see following test.

Environment

  • OS: 4.15.0-47-generic #50-Ubuntu SMP.
  • CPU: Intel(R) Xeon(R) CPU E5-2697 v3 @ 2.60GHz
  • NodeJs: 10.15.3

Normal

function f() {}

async function main() {
    let tm = -Date.now();
    for (let i = 0; i < 100000; i++) {
        f();
    }
    tm += Date.now();
    console.log(tm);
}
main()
$ node test.js
3

With operations

function f() {
    a = [];
    for (let i = 0; i < 30; i++) { a.push(i); }
}

async function main() {
    let tm = -Date.now();
    for (let i = 0; i < 100000; i++) {
        f();
    }
    tm += Date.now();
    console.log(tm);
}
main()

$ node test.js
27

With await

function f() {}

async function main() {
    let tm = -Date.now();
    for (let i = 0; i < 100000; i++) {
        await f();
    }
    tm += Date.now();
    console.log(tm);
}
main()
$ node test.js
19

With async/await

async function f() {}

async function main() {
    let tm = -Date.now();
    for (let i = 0; i < 100000; i++) {
        await f();
    }
    tm += Date.now();
    console.log(tm);
}
main()
$ node test.js
26
블로그 이미지

yhcting

환경:

NodeJS: 8.X

typescript: 2.8.X

jest: 22.X.X

ts-jest: 22.X.X



NodeJS + Typescript를 이용해서 backend를 구성할때, 특정 module을 여러 module에서 공유하고 싶을때가 있다.

이때, 공통 모듈을 package.json 을 가지는 하나의 독립모듈로 구성하고, 이를 dependency에 넣는 것도 좋지만(npm module을 사용하는 일반적인 방법), 그냥 단순하게 module directory를 공용으로 사용하고 싶은 경우도 많다.

예를 들면.


moduleA/

|- package.json

|- src/

|- common (*1)

|- ...


moduleB/

|- package.json

|- src/

|- common (*2)

|- ...


에서 (*1)과 (*2)에서 동일한 코드를 사용하고자 한다고 가정해 보자. 매번 똑같은 파일을 유지하는 것은 문제의 여지가 많으므로, 아래와 같은 구조를 만들고 싶을 것이다.



moduleA/

|- package.json

|- tsconfig.json

|- src/

|- commonA --> (*3) // Symbolic link to (*3)

|- ...


moduleB/

|- package.json

|- tsconfig.json

|- src/

|- commonA --> (*3) // Symbolic link to (*3)

|- ...


shared/

|- commonA/ (*3)

|- ...



그리고, moduleA와 moduleB의 tsconfig.json에는 symbolic link를 쫓아갈 수 있도록 


"compilerOptions": {

...

"preserveSymblinks:" true

...

}


로 설정한다.


자... 이제 모든 것이 문제없이 동작하는 것처럼 보인다.

그런데.. Jest를 수행시키면, 바로 문제가 발생한다.(제대로 Type을 Import하지 못한다!)

그리고, Symbolic link를 제대로 쫓아가지 못하는 것처럼 보인다.

Jest관련 각종 정보를 찾아보면, 일단은, 이 부분은 Known Issue이고, 지원하지 않기로 결정한 것으로 보인다.

(어딘가 본 기억이 있는데, 정확한 link는 잊어 먹었다는... 뭐 이후 또 바뀔수도 있으니....)

그래서, workaround를 고민해 보면, 아래와 같이 그냥 shared module에 ts-jest를 설치해 주면 문제가 해결되는 것 같다.



shared/

|- package.json // only 'ts-jest' exists at 'devDependencies'

|- commonA

|- ...


이렇게 해 두고, shared에서 npm install 을 통해서 ts-jestnode_modules 안쪽에 설치하고 나면, moduleA와 moduleB의 jest가 문제없이 동작하는 것을 볼 수 있다.

더 좋은 방법이 있는지는, 그리고 왜 이렇게 하면 동작하는지는 좀 더 살펴봐야할 부분이긴하나... 굳이 살펴봐야하나... 싶기도 하다... ^_^




블로그 이미지

yhcting


Node 8.9.0 기준

ChildProcess.exec는 shell을 통해서 command를 수행한다.
https://nodejs.org/api/child_process.html 의 아래 설명 참조.


child_process.exec(command[, options][, callback])
    ...
    shell <string> Shell to execute the command with. Default: '/bin/sh' on UNIX, process.env.ComSpec on Windows. See Shell Requirements and Default Windows Shell.
    ...


그래서 주어진 command는 실제로는 "shell => command"로 수행이 되며, exec(...)에 의해서 return되는 process는 shell process이다.
(주의: user command에 의해 수행되는 process가 아니다!)
따라서, return된 process를 kill 해도 실제 user command의 process가 kill되지는 않는다.

이 문제는, exec대신 'execFile' 혹은 'spawn'을 통해서 해결 가능하다.
아래는 spawn관련 document이다.


child_process.spawn(command[, args][, options])#
    ...
    shell <boolean> | <string> If true, runs command inside of a shell. Uses '/bin/sh' on UNIX, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell Requirements and Default Windows Shell. Default: false (no shell).
    ...

즉 spawn은 'shell'을 통하지 않고 바로 수행가능하다.
따라서,

let child = spwn(command[, args], {shell: false})
child.kill()

와 같은 방식은 정상적으로 동작한다.

혹은 execFile 은 기본적으로 shell을 통하지 않으므로, child.kill()이 정상 수행된다.




블로그 이미지

yhcting


<<< co.ts >>>
//
// Module to help escape from callback-hell by using semi-coroutine(generator).
//

function co<E, T>(generator: GeneratorFunction,
        callback: (err: E, result: T) => void) {
    function *wrapper(resolve: any, reject: any) {
        let wrapperGenerator: Generator = yield;
        try {
            let result = yield* (<any>generator)(wrapperGenerator);
            resolve(result);
        } catch (e) {
            reject(e);
        }
    }

    new Promise<T>((resolve, reject) => {
        let w = wrapper(resolve, reject);
        w.next(); // run function
        w.next(w);
    }).then(r => {
        callback(<any>undefined, r);
    }, e => {
        callback(e, <any>undefined);
    });
}

export = co;


<< main.ts >>

import co = require('./co');


describe('co', function() {
    it('co success', done => {
        co<string, Array<Number>>(<any> function* (gen: Generator) {
            let path = new Array<Number>();
            path.push(10);
            setTimeout(() => {
                path.push(20);
                gen.next();
            }, 200);
            yield;
            path.push(30);
            return path;
        }, (err, result) => {
            assert.ok(undefined === err
                && 3 === result.length
                && result[0] === 10
                && result[1] === 20
                && result[2] === 30);
            done();
        });
    });

    it('co fails: throw in generator', done => {
        co<string, string>(<any> function* (gen: Generator) {
            setTimeout(() => {
            }, 200);
            if (gen) {
                throw 'error';
            }
            return 'hello';
        }, (err, result) => {
            assert.ok(undefined === result
                && 'error' === err);
            done();
        });
    });

    /*
     * This test gives unexpected error at mocha. But works well at normal environment.
     *
     *
     * (node:41022) UnhandledPromiseRejectionWarning: Unhandled promise rejection...
     * (node:41022) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated....
     * node_modules/mocha/lib/runner.js:690
     * err.uncaught = true;
     *            ^

     * TypeError: Cannot create property 'uncaught' on string 'error'
     *     at Runner.uncaught ...
     *     ...
     *

    it('co fails: Generator.throw', done => {
        let path = new Array<Number>();
        co<string, string>(<any> function* (gen: Generator) {
            setTimeout(() => {
                gen.throw!('error');
                path.push(10);
                return;
            }, 200);
            return 'done';
        }, (err, result) => {
            assert.ok(undefined === result
                && 'error' === err
                && 1 === path.length);
            done();
        });
    });
    */
});


블로그 이미지

yhcting


< 좋은 link >
http://insanehong.kr/post/javascript-prototype/



< From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new >

The new operator creates an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

...

When the code new Foo(...) is executed, the following things happen:

A new object is created, inheriting from Foo.prototype.
The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.
The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)


< From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype >

...

Object.prototype.constructor
    Specifies the function that creates an object's prototype.

...


--------
즉, 'prototype' property는, 결국 'new' operation에서 object inherit을 위해 사용되는 reserved property이고, instance의 prototype chain은 [[Prototype]](de-facto __proto__) 에 의해 관리된다.

이상에서 다음을 알수(유추할수?) 있다.
- 'prototype' property 는 parent를 가리키는 일반적인 object 이다. (*1)
    : Default로, Function은 자기자신을 constructor로 가지는 prototype을 가진다.
- [[Prototype]]은 'prototype' object 를 가리킨다.

=====================
확실한 이해를 위해 'new' operation의 동작을 확인해 보자.(Tested on console of Chrome-63)


> function A(name) {
    this.date = Date.now();
    this.x = 'Proto';
    this.name = name;
}
undefined

> a = new A('a')
A {date: 1514262817152, x: "Proto", name: "a"}

> b = new A('b')            <= date 값이 매번 달라진다.
A {date: 1514262824795, x: "Proto", name: "b"}

> A.prototype                <= constructor만을 가지는 아주 단순한 object
{constructor: ƒ}
    constructor: ƒ A(name)
    __proto__: Object        <= Object Prototype


> c = Object.create(A.prototype);    <= 단순하게 constructor를 상속하는 과정이라고 생각할 수도 있다.
A {}

> c.constructor('c')            <= Prototype의 constructor를 새롭게 생성된 object에 binding해서 수행한다. (Same with, c.__proto__.constructor.call(c, 'c'))
undefined                   Note: binding of 'this' keyworkd (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)
                       Note: c.__proto__.constructor('c') <= 잘못된 수행. prototype object 자체를 더럽히게 된다.
> c.constructor === c.__proto__.constructor
true

> c
A {date: 1514262907466, x: "Proto", name: "c"}


블로그 이미지

yhcting


Working environment:
OS: Linux 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
nodejs: 8.9.1
typescript: 2.6.2




<< class decorator >>


(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Class decorator
//
///////////////////////////////////////////////////////////////////////////////
/**
 * See ClassDecorator type at lib.es5.d.ts
 * @param ctor constructor function. Prototype of constructor function
 *               is object for class-definition.
 */
function log<T extends Function>(ctor: T): T | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('function' === typeof(ctor));
    console.log('%o', ctor);
    let newConstructor: any = function (...args: Array<any>) {
        console.log('New: ' + ctor.name);
        let o: any = new (<any>ctor)(...args);
        // this is new constructor. So, '_test' is shown at console.log(<instance>)
        o._test = 'mytest';
        return o;
    };
    return newConstructor;
}

@log
class Person {
    constructor(
        public name: string,
        public surname: string
    ) { }

    get myname() {
        return this.name;
    }

    fullname(nick: string) {
        return `${this.name} ${this.surname} : ${nick}`;
    }
}

let p = new Person('name', 'surname');
// console.log('%o, %O', p, p);
console.assert(p.name === 'name');
console.assert(p.myname === 'name');
console.assert(p.fullname('N') === 'name surname : N');
console.assert((<any>p)._test === 'mytest');


///////////////////////////////////////////////////////////////////////////////
//
// Class decorator with argument.
//
///////////////////////////////////////////////////////////////////////////////
function logArg(nick: string) {
    return function<T extends Function>(ctor: T): T | void {
        let newCtor: any = function (...args: Array<any>) {
            // console.log('New: ' + fconstructor.name);
            let o: any = new (<any>ctor)(...args);
            o.nick = nick;
            return o;
        };
        return newCtor;
    };
}

@logArg('nick')
class Person2 {
    constructor(
        public name: string
    ) { }

    fullname(age: number) {
        return `${this.name} ${(<any>this).nick} : ${age}`;
    }
}

let p2 = new Person2('name');
// console.log(p);
console.assert(p2.name === 'name');
console.assert(p2.fullname(18) === 'name nick : 18');
console.assert((<any>p2).nick === 'nick');


///////////////////////////////////////////////////////////////////////////////
})();









<< method decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Member decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 * See MethodDecorator at lib.es5.d.ts for detail types.
 * @param target class(prototype of class-constructor) where this method belongs to
 * @param key name of method
 * @param descriptor method(function)-property descriptor
 */
function log(
    target: any, key: string | symbol, descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);

    // This is ONLY FOR DEMO and EXAMPLE! - NOT RECOMMENDED!
    // Add new property to prototype of class-constructor.
    // (applied to all class instance!)
    target['newattr'] = 'hello';

    // save a reference to the original method this way we keep the values currently in the
    // descriptor and don't overwrite what another decorator might have done to the descriptor.
    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key)!;
    }

    //editing the descriptor/value parameter
    let orig = descriptor.value;
    descriptor.value = function () {
        console.log(`Called: ${key}`);
        return orig!.apply(this, arguments);
    };

    return descriptor;
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    @log
    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
let p2 = new Person('name2');
// console.log(p.fullname('newname', 'nick'));
console.assert('name' === p.myname);
console.assert('name2' === p2.myname);
console.assert('hello' === (<any>p).newattr);
console.assert('hello' === (<any>p2).newattr);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);
console.assert(`hello` === (<any>p).newattr);

console.log(p);

///////////////////////////////////////////////////////////////////////////////
//
// Member decorator with argument
//
///////////////////////////////////////////////////////////////////////////////

// Same way with class decorator.

///////////////////////////////////////////////////////////////////////////////
})();





<< parameter decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Parameter decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 * @param index parameter position in function - stars at 0(the first parameter).
 */
function log(target: any, key: string | symbol, index: number): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);
    console.assert(1 === index);
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    fullname(newname: string, @log nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
// console.log(p.fullname('newname', 'nick'));
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

///////////////////////////////////////////////////////////////////////////////
})();






<< property decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Property decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 */
function log(target: any, key: string | symbol): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('name' === key || 'obj' === key);

    let internalKey = `$${key}`;
    // property getter
    let getter = function () {
        console.log(`Get: ${key} => ${this[internalKey]}`);
        return this[internalKey];
    };

    // property setter
    let setter = function (newVal: any) {
        console.log(`Set: ${key} => ${newVal}`);
        this[internalKey] = newVal;
    };

    // Delete property.
    if (delete target[key]) {
        console.assert(undefined === target[`$${key}`]);
        // Create new property with getter and setter
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class Person {
    @log
    public name = 'init';
    @log
    public obj = {};

    constructor(name: string) {
        this.name = name;
    }

    get myname() {
        return this.name;
    }

    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
p.name = 'hello';
p.obj = {a: 1};

console.assert((<any>p)['$name'] === p.name);
console.assert((<any>p)['$obj'] === p.obj);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

// console.log('%o', p);

///////////////////////////////////////////////////////////////////////////////
})();


블로그 이미지

yhcting