Ed Wright
Ed Wright

Reputation: 33

Why does returning null from a TypeScript method decorator stop other decorators executing?

I'm working through an example of a method decorator in TypeScript, trying to understand how they function. The example can be found here: https://fireship.io/lessons/ts-decorators-by-example/#method-decorator

Essentially there's a Confirmation decorator that the option chosen should be added to an array. This decorator is called placed twice before the method it decorates:

const confirmStatement1 = `Are you sure?`;
const confirmStatement2 = `Are you super sure?`;

class IceCreamComponent {

    toppings = [];

    @Confirmable(confirmStatement1)
    @Confirmable(confirmStatement2)
    addTopping(topping) {
        this.toppings.push(topping);
    }

}

The Decorator code is:

// Method Decorator
function Confirmable(message: string) {
    return function (target: Object, key: string | symbol, descriptor: PropertyDescriptor) {
        const original = descriptor.value;

        descriptor.value = function (...args: any[]) {
            const allow = Math.random() > 0.5 ? true : false;
            console.log(`${message} - allowed value is ${allow}`);

            if (allow) {
                const result = original.apply(this, args);
                return result;
            } else {
                console.log(`Stopping others running...`);
                return null;
            }
        };

        return descriptor;
    };
}

When I do some calling of the code like this:

const myIceCream = new IceCreamComponent();
const possibleToppings = ["Flake", "Salted Caramel Syrup", "Raspberry Syrup", "Chocolate Freeze", "Sprinkles"];

for (let i = 0; i < 5; i++) {
    myIceCream.addTopping(possibleToppings[i]);
}

console.log(myIceCream.toppings);

I get these results:

Are you sure? - allowed value is true
Are you super sure? - allowed value is true
Are you sure? - allowed value is true
Are you super sure? - allowed value is false
Stopping others running...
Are you sure? - allowed value is true
Are you super sure? - allowed value is true
Are you sure? - allowed value is false
Stopping others running...
Are you sure? - allowed value is true
Are you super sure? - allowed value is true
[ 'Flake', 'Raspberry Syrup', 'Sprinkles' ]

Obviously, I have worked out that if allow is false then the anonymous function returns null - and that seems to stop the other decorator from being evaluated.

My question is why? I can't seem to find an answer anywhere...what am I missing?

Upvotes: 1

Views: 838

Answers (1)

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

The returned value from descriptor.value = function (...args) { doesn't pass to the next decorator. It is the original.apply(this, args); invoke actually calls another decorator.

I added more verbose logging as it's not intuitive at first.

  console.log('pre original.apply(this, args);')
  const result = original.apply(this, args);
  console.log('after original.apply(this, args);')
  return result;

Transpiled code in snippet

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
const confirmStatement1 = `Are you sure?`;
const confirmStatement2 = `Are you super sure?`;
function Confirmable(message) {
    return function (target, key, descriptor) {
        const original = descriptor.value;
        descriptor.value = function (...args) {
            const allow = Math.random() > 0.5 ? true : false;
            console.log(`${message} - allowed value is ${allow}`);
            if (allow) {
                console.log('pre original.apply(this, args);');
                const result = original.apply(this, args);
                console.log('after original.apply(this, args);');
                return result;
            }
            else {
                console.log(`Stopping others running...`);
                return null;
            }
        };
        return descriptor;
    };
}
class IceCreamComponent {
    constructor() {
        this.toppings = [];
    }
    addTopping(topping) {
        this.toppings.push(topping);
    }
}
__decorate([
    Confirmable(confirmStatement1),
    Confirmable(confirmStatement2)
], IceCreamComponent.prototype, "addTopping", null);
const myIceCream = new IceCreamComponent();
const possibleToppings = ["Flake", "Salted Caramel Syrup", "Raspberry Syrup", "Chocolate Freeze", "Sprinkles"];
for (let i = 0; i < 5; i++) {
    myIceCream.addTopping(possibleToppings[i]);
}
console.log(myIceCream.toppings);

Upvotes: 1

Related Questions