То, что вам на самом деле здесь нужно, называется необязательное сцепление :
obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null
?.
- это экзистенциальный оператор, который позволяет вам безопасно обращаться к свойствам и не выдает, еслисобственность отсутствует.Однако необязательное сцепление еще не является частью JavaScript , но было предложено, см. Состояние 1 TC39 .
Но, с использованием прокси и Maybe
класс, вы можете реализовать необязательную цепочку и возвращать значение по умолчанию, когда цепочка не работает.
Функция wrap()
используется для обтекания объектов, к которым вы хотите применить необязательную цепочку.Внутренне wrap
создает прокси вокруг вашего объекта и управляет пропущенными значениями с помощью оболочки Maybe
.
В конце цепочки вы разворачиваете значение, объединяя в цепочку getOrElse(default)
со значением по умолчанию, котороевозвращается, когда цепочка недействительна:
const obj = {
a: 1,
b: {
c: [4, 1, 2]
},
c: () => 'yes'
};
console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
Полный пример:
class Maybe {
constructor(value) {
this.__value = value;
}
static of(value){
if (value instanceof Maybe) return value;
return new Maybe(value);
}
getOrElse(elseVal) {
return this.isNothing() ? elseVal : this.__value;
}
isNothing() {
return this.__value === null || this.__value === undefined;
}
map(fn) {
return this.isNothing()
? Maybe.of(null)
: Maybe.of(fn(this.__value));
}
}
function wrap(obj) {
function fix(object, property) {
const value = object[property];
return typeof value === 'function' ? value.bind(object) : value;
}
return new Proxy(Maybe.of(obj), {
get: function(target, property) {
if (property in target) {
return fix(target, property);
} else {
return wrap(target.map(val => fix(val, property)));
}
}
});
}
const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2