18

I've been programming a lot in Swift recently. Today I did some work in JavaScipt when question popped up to me:

Is there something similar to optional chaining in JavaScript? A way to prevent undefined is not an object without any variables?

Example:

function test(){
   if(new Date() % 2){
      return {value: function(){/*code*/}};
   }
} 

test().value();

will fail half of time because sometimes test returns undefined.

The only solution I can think of is a function:

function oc(object, key){
   if(object){
      return object[key]();
   }
}

oc(test(), 'value');

I would like to be able to do something like:

test()?.value()

The part after the question mark is only executed if test returned an object.

But this is not very elegeant. Is there something better? A magic combination of operators?

Edit I know I could rewrite test to return something. But I'm wondering if there's something like optional chaining. I'm not interested in a particular solution to the above example. Something that I also can use if have no control over the function returning undefined.

Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
idmean
  • 13,418
  • 7
  • 47
  • 78
  • what you want value() to return when test() return undefined? – Zaheer Ahmed Oct 03 '14 at 17:44
  • @ZaheerAhmed false or undefined. It does not matter really. – idmean Oct 03 '14 at 17:45
  • you can wrap the existing function to return a default value in the case of undefined, without rewriting. you can also use a ternary (x?y:z)() or default operator (x||y)() at call-time. – dandavis Oct 03 '14 at 17:54
  • @dandavis But that would me require to write a wrapper function for each function I want to call. And what should I return in this functions? (Look at my 2nd example) – idmean Oct 03 '14 at 17:57
  • 1
    i was thinking you could use the same wrapper on each one you wanted to default, even with little more than an array of function names and one map(), but maybe i don't understand the goal. functions are very malable in JS, so it should not take repetition or boilerplate. RE: edit, "(x=test()) && x.value()" is shorter than a wrapper, or if you get back a truthy instead of an object sometimes, (x=test()) && x.value && x.value(); but a re-usable function that operates on functions would leave behind cleaner code. – dandavis Oct 03 '14 at 17:58
  • Not possible in JS, but you can simplify the [workaround](http://jsfiddle.net/0p49xpj5/) using `&&`. – Teemu Oct 03 '14 at 18:03
  • 2
    Yeah, I know that. I was just wondering if there is something like optional chaining in JavaScript because JS is full of surprises. – idmean Oct 03 '14 at 18:05

8 Answers8

19

This is currently a Stage 4 proposal you can check on the progress of it here:
https://github.com/tc39/proposal-optional-chaining

You can use the babel plugin today:
https://www.npmjs.com/package/babel-plugin-transform-optional-chaining

Update 11th January 2020: Babel now supports optional chaining by default https://babeljs.io/blog/2020/01/11/7.8.0

The Optional Chaining operator is spelled ?.. It may appear in three positions:

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

Notes:

In order to allow foo?.3:0 to be parsed as foo ? .3 : 0 (as required for backward compatibility), a simple lookahead is added at the level of the lexical grammar, so that the sequence of characters ?. is not interpreted as a single token in that situation (the ?. token must not be immediately followed by a decimal digit).

Also worth checking out:

https://github.com/tc39/proposal-nullish-coalescing

https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-nullish-coalescing-operator

FZs
  • 11,931
  • 11
  • 28
  • 41
Jonathan Wood
  • 871
  • 9
  • 19
7

In plain JavaScript you have to do type checks or structure your code so that you know an object will exist.

CoffeeScript, a language that compiles down to JavaScript, provides an existential operator ?. for safe chaining if you're willing to consider a preprocessed language.

There's another discussion here about why you can't reproduce this behavior in JS.

There is also a discussion on the ESDiscuss forums about adding an existential operator to a future version of JavaScript. It doesn't seem very far along though, certainly nowhere close to practical use. More of an idea at this point.

Community
  • 1
  • 1
Ben McCormick
  • 23,365
  • 11
  • 48
  • 70
  • 2
    I've accepted your answer because it really answers my question unlike the other answers below and because it provides some interesting information. – idmean Oct 03 '14 at 17:56
4

You can use

test() && test().value();

or

var testResult = test();
testResult && testResult.value();

If you ask me this is most similar to Swift's optional chaining.

George Chernov
  • 258
  • 3
  • 5
  • `test() && test().value()` is inappropriate if `test` has side-effects or if `test` returns a falsy value other than `null` or `undefined`, and of course it still fails if `test()` doesn’t have a `value` property, or its `value` property isn’t a function. – Sebastian Simon Apr 09 '20 at 22:42
2

Optional chaining has landed in JS. We can use optional chaining via the ?. operator in object property access. It allows us to try accessing properties of objects which might not exists (i.e. are undefined) without throwing an error.

Here is a code example:

const obj = {
  foo: {
    bar: 1
  }
};

// If we try to access property which doesn't exists
// it just returns undefined
console.log(obj.baz);

try {
  // Now we try to access a property of undefined which throws an error
  obj.baz.foz;
} catch (e) {
  console.dir(e.message);
}

// Now we use the optional chaining operator ?.
// We get undefined instead of an error
console.log(obj.baz?.foz);
            
console.log(obj.foo?.bar);
Willem van der Veen
  • 19,609
  • 11
  • 116
  • 113
1

var Obj = {Prop: {name: 'peter'}}

console.log(Obj.Prop.name) console.log(Obj?.Prop?.name)

In the first sentence, you're just accessing object properties. The problem with that is that if you find Prop to be something other than an object, it will throw an exception. That's the reason of the optional chainig operator.

Lets say you try to do Obj.Prop2.name. You'll get Uncaught TypeError: Cannot read property 'name' of undefined if Instead you did Obj.Prop2?.name, You'll only receive undefined as a value, instead of an exception.

This is particularly useful when accessing deeply nested properties.

WARNING: This is a relatively new JS feature that's not yet implemented in all browsers, so be careful while using it for production applications.

Pablo Marcano
  • 911
  • 7
  • 6
0

What about returning a noop function that does nothing when the condition isn't met?

function test(){
   if(new Date() % 2){
      return {value: function(){/*code*/}};
   }
   return {value: function(){ /* just return a type consistent with the function above */ }
} 
sebnukem
  • 7,217
  • 5
  • 35
  • 45
0

You can always return this; if test is a object method.

But why do you want to prevent such errors by creating mock functions?

veritas
  • 1,966
  • 1
  • 15
  • 29
0

Optional Chaining is finally in the JavaScript standard!

Here are a few examples:

// properties
foo?.bar
foo?.bar()
foo?.bar.baz()
foo?.bar?.baz()

// indexing
foo?.[0]
foo?.['bar']

// check if a function is defined before invoking
foo?.()
foo.bar?.()
foo?.bar?.()

And this is way better than what most people use for manually checking for nulls

Instead of evaluating

foo?.bar

to this little code snippet we are all used to writing

foo ? foo.bar : null

it actually evaluates to

foo == null ? undefined : foo.bar

which works for all the falsey values like an empty string, 0 or false.


Unrelated to the question, but you might also be interested in the ?? operator.

It has a similar purpose as || except it only checks for null or undefined.

For example:

foo ?? bar

would be the same as:

foo != null ? foo : bar

This is a very new feature, so even thought a lot of users already use a browser that supports this you will still want to use a tool to convert it to an older version of javascript.

zoran404
  • 1,090
  • 14
  • 30