KEMBAR78
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018) | PDF
Missing objects:
?. and ?? in JavaScript
Daniel Ehrenberg @littledan
Igalia, in partnership with Bloomberg
TC39 delegate
BrazilJS 2018
Optional chaining ?.
for
Safe property access
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
const passportNumbers = people.map(person => person.passport.number)
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
const passportNumbers = people.map(person => person.passport.number)
// ===> TypeError!
// people[1].passport is undefined,
// so it has no number property
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
// Fix:
const passportNumbers = people.map(person => {
if (person.passport) return person.passport.number;
else return undefined;
});
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
// Shorter fix:
const passportNumbers = people.map(person =>
person.passport && person.passport.number);
const people = [
{name: "Dan Ehrenberg,
passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}},
{name: "John Doe",
dateOfBirth: new Date(2000, 5, 5)}
];
// With optional chaining:
const passportNumbers = people.map(person =>
person.passport?.number);
Avoid repetition
â—Ź Repetition with &&
a && a.b && a.b.c && a.b.c.d
â—Ź Don't repeat yourself with ?.
a?.b?.c?.d
Falsiness with &&
const a = { b: 0 };
a && a.b && a.b.toString()
// returns 0
if (0) // takes else branch
if (false) // takes else branch
if (undefined) // takes else branch
if (null) // takes else branch
if ("") // takes else branch
// Similar treatment from &&, ||, etc
0 && 1 // ⇒ 0
false && 1 // ⇒ false
undefined && 1 // ⇒ undefined
null && 1 // ⇒ null
"" && 1 // ⇒ ""
true && 1 // ⇒ 1
Avoid falsiness with ?.
let a = { b: 0 };
a?.b?.toString()
// returns "0"
Exerpted from Wikipedia:
https://en.wikipedia.org/wiki/Safe_navigation_operator
Usable in:
â—Ź Coffeescript
â—Ź Groovy
â—Ź C#
â—Ź PHP
â—Ź Kotlin
â—Ź Angular Templates
â—Ź Ruby
â—Ź Perl 6
â—Ź Swift
â—Ź And maybe soon,
JavaScript!
Credit: Oskari Noppa in https://github.com/tc39/proposal-optional-chaining/issues/69#issuecomment-409045384
Nullish coalescing ??
for
default options
Source: Matthew Zeunert, https://www.codereadability.com/what-are-javascript-options-objects/
function findTimeout(options) {
if (options.timeout)
return options.timeout;
else
return 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
function findTimeout(options) {
// Popular shorthand
return options.timeout || 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
function findTimeout(options) {
if (options.timeout)
return options.timeout;
else
return 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
function findTimeout(options) {
if (options.timeout)
return options.timeout;
else
return 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
findTimeout({ timeout: 0 }); // ⇒ timeout ===
function findTimeout(options) {
// Popular shorthand
return options.timeout || 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
findTimeout({ timeout: 0 }); // ⇒ timeout ===
if (0) // takes else branch
if (false) // takes else branch
if (undefined) // takes else branch
if (null) // takes else branch
if ("") // takes else branch
// Similar treatment from &&, ||, etc
undefined || 1 // ⇒ 1
null || 1 // ⇒ 1
0 || 1 // ⇒ 1
"" || 1 // ⇒ 1
false || 1 // ⇒ 1
true || 1 // ⇒ true
function findTimeout(options) {
if (options.timeout !== undefined)
return options.timeout;
else
return 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1
findTimeout({ timeout: 0 }): // ⇒ timeout === 0
function findTimeout(options) {
if (typeof options.foo !== 'undefined')
return options.timeout;
else
return 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1
findTimeout({ timeout: 0 }): // ⇒ timeout === 0
function findTimeout(options) {
return options.foo ?? 100;
}
findTimeout({}); // ⇒ timeout === 100
findTimeout({ timeout: 1 }); // ⇒ timeout === 1
findTimeout({ timeout: 0 }): // ⇒ timeout === 0
undefined ?? 1 // ⇒ 1
null ?? 1 // ⇒ 1
0 ?? 1 // ⇒ 0
"" ?? 1 // ⇒ ""
false ?? 1 // ⇒ false
?? is the new ||
const response = {
settings: {
duration: 0,
splashScreen: false
}
};
const duration = response.settings?.duration ?? 300; // 0
const splashScreen = response.settings?.splashScreen ?? true; // false
const headerText = response.settings?.headerText ?? "Header"; // "Header"
Works great together
But is all
that JS?
Maybe it will be soon!
We are looking into it
Optional chaining (?.)
& nullish coalescing (??)
are at Stage 1
Champion: Gabriel Isenberg
// ES6 Destructuring Assignment
let x = 1, y = 2;
[y, x] = [x, y];
⇒ x === 2
y === 1
Who is TC39?
● A committee of Ecma, with…
â—Ź JS developers
â—Ź Implementers
â—Ź Frameworks
â—Ź Academics
â—Ź etc
Meetings
â—Ź Every two months
â—Ź For three days
â—Ź Summarize/review GitHub activity
â—Ź Move proposals through stages
TC39 stages
â—Ź Stage 1: An idea
â—Ź Stage 2: Agreed-on first draft
â—Ź Stage 3: Details worked out
â—Ź Stage 4: Battle-tested and standard
?. and ?? each reached Stage 1
â—Ź The committee expects to discuss it âś”
â—Ź Gabriel Isenberg presented them to TC39,
In 2017
Next steps for
?. and ??
Steps needed for Stage 2
â—Ź First draft spec text âś”
● Agreement on syntax and semantics …
● Agreement that we want the proposal …
Details:
Method calls and
property access syntax
// Most languages:
obj.method?(args)
obj?[index]
// JS:
obj.method?.(args)
obj?.[index]
// JS:
obj.method?.(args)
obj?.[index]
// Why?
// JS syntax tries to avoid lots of lookahead
// Ambiguity with ternary operator ?:
obj?[index]:bar
obj.method?(args):bar
// Alternate proposal:
obj??.property
obj.method??(args)
obj??[index]
obj ??? default
Details:
Handling of null
// null is treated as missing, like undefined
null ?? x // ⇒ x
(null)?.x // ⇒ undefined
// Handle null as not missing?
Bigger picture:
Do we need a new
feature?
Add these features?
â—Ź Pro
â—‹ Makes some code cleaner
â—‹ Avoids bugs from existing idioms
â—Ź Con
â—‹ More to learn, mentally parse, etc
Steps needed for Stage 2
â—Ź First draft spec text âś”
● Agreement on syntax, broadly …
● Agreement that we want the proposal …
Plan from here
â—Ź Experiment with ?. and ?? in Babel, at Stage 1
â—Ź Collect feedback over time
â—Ź Consider for Stage 2 in the future
â—Ź The features are easy to implement; we are just
taking our time to work out the right design.
Steps needed for Stage 3
● Complete spec text …
● Signoff from reviewers, editor …
● Ready to pass off to implementers …
Steps needed for Stage 4
● Two implementations …
● Conformance tests …
● PR with editor signoff …
Getting involved
Feedback on GitHub issues
â—Ź Does this work for you?
â—Ź Missing pieces?
â—Ź Handle a case differently?
?. GitHub feedback
â—Ź Syntax: ?. vs ?&. vs ??.
â—Ź Null handling
â—Ź Which operations support
optional chaining
â—Ź And more!
Test262 tests
â—Ź Tests shared between JS engines
● “If it’s not tested, it’s not compatible”
â—Ź Finds edge cases
?./?? test262 tests
â—Ź ...None yet
â—Ź Could be useful for Babel development
â—Ź Wanna help?
Documentation/Education
â—Ź Explain the feature to contributors, learners
â—Ź Understand mental models for JS
?./?? documentation
â—Ź "Explainer documents" for each proposal
â—Ź Wanted: Better introductory materials
â—Ź Wanted: Educators' perspective on the proposals
Implementation
â—Ź Open-source projects accepting contributions:
â—‹ Babel
â—‹ TypeScript
â—‹ V8
â—‹ Acorn
â—Ź Features developed behind a flag/in a plugin
â—‹ JSC
â—‹ ChakraCore
â—‹ SpiderMonkey
â—‹ And many more
?./?? implementations
â—Ź Babel transforms in 7.0
â—‹ @babel/plugin-proposal-nullish-coalescing-operator
â—‹ @babel/plugin-proposal-optional-chaining
â–  Included in React Native 0.56
â—‹ Thanks, Justin Ridgewell and Lucas Azzola!
â—Ź Want to write more implementations? Try this out?
Attending TC39 as a delegate
â—Ź Join Ecma to come to TC39 meetings
â—Ź Contact me for more info, littledan@igalia.com
â—Ź Get involved in TC39:
https://tc39.github.io/
â—Ź ?. and ?? are at Stage 1
for handling null/undefined
â—Ź Daniel Ehrenberg
â—Ź Twitter/GitHub @littledan
â—Ź littledan@igalia.com

Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)

  • 1.
    Missing objects: ?. and?? in JavaScript Daniel Ehrenberg @littledan Igalia, in partnership with Bloomberg TC39 delegate BrazilJS 2018
  • 2.
  • 3.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ];
  • 4.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ]; const passportNumbers = people.map(person => person.passport.number)
  • 5.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ]; const passportNumbers = people.map(person => person.passport.number) // ===> TypeError! // people[1].passport is undefined, // so it has no number property
  • 6.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ]; // Fix: const passportNumbers = people.map(person => { if (person.passport) return person.passport.number; else return undefined; });
  • 7.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ]; // Shorter fix: const passportNumbers = people.map(person => person.passport && person.passport.number);
  • 8.
    const people =[ {name: "Dan Ehrenberg, passport: {number: 1234567n, expiration: new Date(2020, 1, 1)}}, {name: "John Doe", dateOfBirth: new Date(2000, 5, 5)} ]; // With optional chaining: const passportNumbers = people.map(person => person.passport?.number);
  • 9.
    Avoid repetition â—Ź Repetitionwith && a && a.b && a.b.c && a.b.c.d â—Ź Don't repeat yourself with ?. a?.b?.c?.d
  • 10.
    Falsiness with && consta = { b: 0 }; a && a.b && a.b.toString() // returns 0
  • 11.
    if (0) //takes else branch if (false) // takes else branch if (undefined) // takes else branch if (null) // takes else branch if ("") // takes else branch // Similar treatment from &&, ||, etc
  • 12.
    0 && 1// ⇒ 0 false && 1 // ⇒ false undefined && 1 // ⇒ undefined null && 1 // ⇒ null "" && 1 // ⇒ "" true && 1 // ⇒ 1
  • 13.
    Avoid falsiness with?. let a = { b: 0 }; a?.b?.toString() // returns "0"
  • 14.
    Exerpted from Wikipedia: https://en.wikipedia.org/wiki/Safe_navigation_operator Usablein: â—Ź Coffeescript â—Ź Groovy â—Ź C# â—Ź PHP â—Ź Kotlin â—Ź Angular Templates â—Ź Ruby â—Ź Perl 6 â—Ź Swift â—Ź And maybe soon, JavaScript!
  • 15.
    Credit: Oskari Noppain https://github.com/tc39/proposal-optional-chaining/issues/69#issuecomment-409045384
  • 16.
  • 17.
    Source: Matthew Zeunert,https://www.codereadability.com/what-are-javascript-options-objects/
  • 18.
    function findTimeout(options) { if(options.timeout) return options.timeout; else return 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
  • 19.
    function findTimeout(options) { //Popular shorthand return options.timeout || 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
  • 20.
    function findTimeout(options) { if(options.timeout) return options.timeout; else return 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1;
  • 21.
    function findTimeout(options) { if(options.timeout) return options.timeout; else return 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1; findTimeout({ timeout: 0 }); // ⇒ timeout ===
  • 22.
    function findTimeout(options) { //Popular shorthand return options.timeout || 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1; findTimeout({ timeout: 0 }); // ⇒ timeout ===
  • 23.
    if (0) //takes else branch if (false) // takes else branch if (undefined) // takes else branch if (null) // takes else branch if ("") // takes else branch // Similar treatment from &&, ||, etc
  • 24.
    undefined || 1// ⇒ 1 null || 1 // ⇒ 1 0 || 1 // ⇒ 1 "" || 1 // ⇒ 1 false || 1 // ⇒ 1 true || 1 // ⇒ true
  • 25.
    function findTimeout(options) { if(options.timeout !== undefined) return options.timeout; else return 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1 findTimeout({ timeout: 0 }): // ⇒ timeout === 0
  • 26.
    function findTimeout(options) { if(typeof options.foo !== 'undefined') return options.timeout; else return 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1 findTimeout({ timeout: 0 }): // ⇒ timeout === 0
  • 27.
    function findTimeout(options) { returnoptions.foo ?? 100; } findTimeout({}); // ⇒ timeout === 100 findTimeout({ timeout: 1 }); // ⇒ timeout === 1 findTimeout({ timeout: 0 }): // ⇒ timeout === 0
  • 28.
    undefined ?? 1// ⇒ 1 null ?? 1 // ⇒ 1 0 ?? 1 // ⇒ 0 "" ?? 1 // ⇒ "" false ?? 1 // ⇒ false ?? is the new ||
  • 29.
    const response ={ settings: { duration: 0, splashScreen: false } }; const duration = response.settings?.duration ?? 300; // 0 const splashScreen = response.settings?.splashScreen ?? true; // false const headerText = response.settings?.headerText ?? "Header"; // "Header" Works great together
  • 30.
  • 31.
    Maybe it willbe soon! We are looking into it Optional chaining (?.) & nullish coalescing (??) are at Stage 1 Champion: Gabriel Isenberg
  • 32.
    // ES6 DestructuringAssignment let x = 1, y = 2; [y, x] = [x, y]; ⇒ x === 2 y === 1
  • 33.
    Who is TC39? ●A committee of Ecma, with… ● JS developers ● Implementers ● Frameworks ● Academics ● etc
  • 36.
    Meetings â—Ź Every twomonths â—Ź For three days â—Ź Summarize/review GitHub activity â—Ź Move proposals through stages
  • 37.
    TC39 stages â—Ź Stage1: An idea â—Ź Stage 2: Agreed-on first draft â—Ź Stage 3: Details worked out â—Ź Stage 4: Battle-tested and standard
  • 38.
    ?. and ??each reached Stage 1 â—Ź The committee expects to discuss it âś” â—Ź Gabriel Isenberg presented them to TC39, In 2017
  • 39.
  • 40.
    Steps needed forStage 2 ● First draft spec text ✔ ● Agreement on syntax and semantics … ● Agreement that we want the proposal …
  • 41.
  • 42.
    // Most languages: obj.method?(args) obj?[index] //JS: obj.method?.(args) obj?.[index]
  • 43.
    // JS: obj.method?.(args) obj?.[index] // Why? //JS syntax tries to avoid lots of lookahead // Ambiguity with ternary operator ?: obj?[index]:bar obj.method?(args):bar
  • 44.
  • 45.
  • 46.
    // null istreated as missing, like undefined null ?? x // ⇒ x (null)?.x // ⇒ undefined // Handle null as not missing?
  • 47.
    Bigger picture: Do weneed a new feature?
  • 48.
    Add these features? â—ŹPro â—‹ Makes some code cleaner â—‹ Avoids bugs from existing idioms â—Ź Con â—‹ More to learn, mentally parse, etc
  • 49.
    Steps needed forStage 2 ● First draft spec text ✔ ● Agreement on syntax, broadly … ● Agreement that we want the proposal …
  • 50.
    Plan from here â—ŹExperiment with ?. and ?? in Babel, at Stage 1 â—Ź Collect feedback over time â—Ź Consider for Stage 2 in the future â—Ź The features are easy to implement; we are just taking our time to work out the right design.
  • 51.
    Steps needed forStage 3 ● Complete spec text … ● Signoff from reviewers, editor … ● Ready to pass off to implementers …
  • 52.
    Steps needed forStage 4 ● Two implementations … ● Conformance tests … ● PR with editor signoff …
  • 53.
  • 54.
    Feedback on GitHubissues â—Ź Does this work for you? â—Ź Missing pieces? â—Ź Handle a case differently?
  • 55.
    ?. GitHub feedback â—ŹSyntax: ?. vs ?&. vs ??. â—Ź Null handling â—Ź Which operations support optional chaining â—Ź And more!
  • 56.
    Test262 tests ● Testsshared between JS engines ● “If it’s not tested, it’s not compatible” ● Finds edge cases
  • 58.
    ?./?? test262 tests â—Ź...None yet â—Ź Could be useful for Babel development â—Ź Wanna help?
  • 59.
    Documentation/Education â—Ź Explain thefeature to contributors, learners â—Ź Understand mental models for JS
  • 61.
    ?./?? documentation â—Ź "Explainerdocuments" for each proposal â—Ź Wanted: Better introductory materials â—Ź Wanted: Educators' perspective on the proposals
  • 62.
    Implementation â—Ź Open-source projectsaccepting contributions: â—‹ Babel â—‹ TypeScript â—‹ V8 â—‹ Acorn â—Ź Features developed behind a flag/in a plugin â—‹ JSC â—‹ ChakraCore â—‹ SpiderMonkey â—‹ And many more
  • 64.
    ?./?? implementations â—Ź Babeltransforms in 7.0 â—‹ @babel/plugin-proposal-nullish-coalescing-operator â—‹ @babel/plugin-proposal-optional-chaining â–  Included in React Native 0.56 â—‹ Thanks, Justin Ridgewell and Lucas Azzola! â—Ź Want to write more implementations? Try this out?
  • 65.
    Attending TC39 asa delegate â—Ź Join Ecma to come to TC39 meetings â—Ź Contact me for more info, littledan@igalia.com
  • 66.
    â—Ź Get involvedin TC39: https://tc39.github.io/ â—Ź ?. and ?? are at Stage 1 for handling null/undefined â—Ź Daniel Ehrenberg â—Ź Twitter/GitHub @littledan â—Ź littledan@igalia.com