Search code examples
javascriptecmascript-next

ES2020 optional chaining: what's the difference between a?.().b and a()?.b and a?.()?.b


Suppose we have this object:

let obj = {};

What exactly each of these expressions do?

  • obj.a?.().b
  • obj.a()?.b
  • obj.a?.()?.b

Solution

  • obj.a?.().b

    1. Dereference obj directly.

    2. Dereference obj.a in a null-safe way - it will stop at this point if the property is not there, it is undefined or null. If that happens the result of evaluating the expression will be undefined.

    3. Execute that value directly.

    4. Receive the return result and continue.

    5. Get the property b from the result directly.

    Flowchart for the above process

    const tryIt = obj => {
      console.log("------start------");
    
      console.log("trying with", obj );
    
      try {
        console.log( "result", obj.a?.().b );
      } catch (e) {
        console.error("problem", e.message);
      }
    
      console.log("-------end-------");
    }
    
    tryIt( null );                 // ERROR
    tryIt( {} );                   // undefined
    tryIt( { a: undefined } );     // undefined
    tryIt( { a: null } );          // undefined
    tryIt( { a: false } );         // ERROR
    tryIt( { a: "hello" } );       // ERROR
    tryIt( { a: function() {} } ); // ERROR
    tryIt( {                       // ERROR
      a: function() {
        return null; 
      }
    });
    tryIt( {                       // 42
      a: function() {
        return { b: 42 };
      }
    });

    obj.a()?.b

    1. Dereference obj directly.

    2. Dereference obj.a directly.

    3. Executing that value directly.

    4. Handle the value in a null-safe way - it will stop at this point if the return value is null or undefined. If that happens the result of evaluating the expression will be undefined.

    5. Get the property b from the result directly.

    Flowchart for the above process

    const tryIt = obj => {
      console.log("------start------");
    
      console.log("trying with", obj );
    
      try {
        console.log( "result", obj.a()?.b );
      } catch (e) {
        console.error("problem", e.message);
      }
    
      console.log("-------end-------");
    }
    
    
    tryIt( null );                 // ERROR
    tryIt( {} );                   // ERROR
    tryIt( { a: undefined } );     // ERROR
    tryIt( { a: null } );          // ERROR
    tryIt( { a: false } );         // ERROR
    tryIt( { a: "hello" } );       // ERROR
    tryIt( { a: function() {} } ); // undefined
    tryIt( {                       // undefined
      a: function() {
        return null; 
      }
    });
    tryIt( {                       // 42
      a: function() {
        return { b: 42 };
      }
    });

    obj.a?.()?.b

    1. Dereference obj directly.

    2. Dereference obj.a in a null-safe way - it will stop at this point if the property is not there, it is undefined or null. If that happens the result of evaluating the expression will be undefined.

    3. Executing that value directly.

    4. Handle the value in a null-safe way - it will stop at this point if the return value is null or undefined. If that happens the result of evaluating the expression will be undefined.

    5. Get the property b from the result directly.

    enter image description here

    const tryIt = obj => {
      console.log("------start------");
    
      console.log("trying with", obj );
    
      try {
        console.log( "result", obj.a?.()?.b );
      } catch (e) {
        console.error("problem", e.message);
      }
    
      console.log("-------end-------");
    }
    
    
    tryIt( null );                 // ERROR
    tryIt( {} );                   // undefined
    tryIt( { a: undefined } );     // undefined
    tryIt( { a: null } );          // undefined
    tryIt( { a: false } );         // ERROR
    tryIt( { a: "hello" } );       // ERROR
    tryIt( { a: function() {} } ); // undefined
    tryIt( {                       // undefined
      a: function() {
        return null; 
      }
    });
    tryIt( {                       // 42
      a: function() {
        return { b: 42 };
      }
    });