nas5w / javascript-tips-and-tidbits

A continuously-evolving compendium of javascript tips based on common areas of confusion or misunderstanding.
MIT License
1.2k stars 73 forks source link

Explaining var, let and const. Using const in examples when reference is not reassigned. Adding an example of promise chaining. Pointing out that an async function will return a promise. #12

Open JasonMatthewsDev opened 5 years ago

JasonMatthewsDev commented 5 years ago

I have a few changes I'd like to submit. The first is largely opinion based but I think the community adopting a consistent style will ease the onboarding process for those getting into the language. I think the examples should be using const instead of let when the reference is not reassigned. Airbnb has a really good style guide for javascript and while it's not a bible it's a good launching point for those developing their own styles IMO. https://github.com/airbnb/javascript#references--prefer-const

I'd like to add an explanation and example for var, let and const, something along the lines of:

Var, Let, Const

Var is basically antiquated at this point. It was the pre-ES6 way to declare a variable and comes with some gotchas. The declarations are function scoped and var declarations get hoisted to the top of the function which can lead to some unexpected behavior.

function varCheck() {
  console.log(x); // => Uncaught ReferenceError: x is not defined
}
var x = 1;
function varCheck() {
  console.log(x); // => undefined
  {
    var x = 2;
    console.log(x); // => 2
  }
}

Let is the way to declare a variable in ES6. It can be reassigned after declaration and is block scoped.

let x = 1;
function letCheck() {
  console.log(x); // => 1
  {
    let x = 2;
    console.log(x); // => 2
  }
  console.log(x): // => 1
}

Const is the way to declare a constant in ES6. The variable can not be assigned after declaration.

const x = 1;
x = 2; // => SyntaxError: Assignment to constant variable: x at 2:0

There is some debate on whether or not const should be used for non primitive types that are mutated but the reference is not reassigned.

const myArray = [1, 2, 3];
myArray.pop();
console.log(myArray); // => [1, 2]

Is valid. If you're unsure airbnb has a fantastic style guide to reference. https://github.com/airbnb/javascript#references--prefer-const

I think that an example of promise chaining is probably a good idea.

.then methods can be chained. I see a lot of new comers end up in some kind of call back hell inside of a promise when it's completely unnecessary.

//The wrong way
getSomedata
  .then(data => {
    getSomeMoreData(data)
      .then(newData => {
        getSomeRelatedData(newData)
          .then(finalData => {
            console.log(newData);
          });
        });
      });
  });
//The right way
getSomeData
  .then(data => {
    return getSomeMoreData(data);
  })
  .then(data => {
    return getSomeRelatedData(data);
  })
  .then(data => {
    console.log(data);
  });

You can see how it's much easier to read the second form and with ES6 implicit returns we could even simplify that further

getSomeData
  .then(data => getSomeMoreData(data))
  .then(data => getSomeRelatedData(data))
  .then(data => console.log(data));

// Because the function supplied to .then will be called with the the result of the resolve method from the promise we can omit the ceremony of creating an anonymous function altogether. This is equivalent to above
getSomeData
  .then(getSomeMoreData)
  .then(getSomeRelatedData)
  .then(console.log);

In the async await section I think it's a good idea to point out that an async function will return a promise

Note: One important thing to note here is that the result of an async function is a promise

async function myFunc() {
  return await greeter;
}

console.log(myFunc()) // => Promise {}

myFunc().then(console.log); // => Hello world!
Andrei0872 commented 5 years ago

Speaking of promises and async await, I'd also like to point out the difference between Promise.all() and for-await-of.

Consider the following starting points:

const fetch = (name, time) => new Promise((resolve, reject) => setTimeout(resolve, time, name));
const requests = [fetch('bill', 1000), fetch('jane', 3000), fetch('john', 500)];

Promise.all() All requests are fired at the same time, but Promise.all() finishes when the longer promise is solved.

const results = await Promise.all(requests);
results.forEach(name => console.log(name));
/*
bill
jane
john
--> after 3 seconds
*/

for-await-of In order to use for-await-of, the array must implement the Symbol.asyncIterator protocol.

All the requests are fired at the same time, but fetching jane takes longer than fetching john, so john must wait until jane is ready. This means that for-await-of solves a promise if it's fulfilled and the previous one has already been solved.

 requests[Symbol.asyncIterator] = async function* () {
        // yield* this.map(async req => await req);
        // This works as well
        yield* [... this];
    }

for await (const name of requests) {
    console.log(name)
}
/*
bill --> after 1 second
jane --> after 3 seconds
john --> after 3 seconds
--> jane and john will be printed instantly
*/
nas5w commented 5 years ago

@jmatty1983 I'm grappling with the var, let, const discussion. Where is the line between this being a list of "tips" versus growing into a reference manual? It's certainly been a bit arbitrary so far and probably more driven by the topics I found relatively tricky when I learned them.

With respect to Promise chaining and avoiding the nesting anti-pattern, that's definitely a terrific addition because I've seen newcomers doing that too. I created issue #13 for it. Would you want to do a pull request? I think this would be in a subsection within the Promises section.

The async await clarification re: returning a Promise is good too if you'd like to add it.

Thanks!

JasonMatthewsDev commented 5 years ago

Sure I'll submit a pull request for the promise addition and the async await clarification. I leave the figuring out where the line is of reference manual / tips up to you on the other topic.

Leamsi9 commented 5 years ago

@jmatty1983 I'm grappling with the var, let, const discussion. Where is the line between this being a list of "tips" versus growing into a reference manual? It's certainly been a bit arbitrary so far and probably more driven by the topics I found relatively tricky when I learned them.

So many tutorials and therefore learners are still using vars that having a tip around var-related gotchas would probably be helpful. One common gotcha in the wild is hoisting. Another common gotcha is the differing behaviour of var and let, like in this brilliant and I think realistic example from MDN:

var a = [];
(function () {
   'use strict';
   for (let i = 0; i < 5; ++i) { // *** `let`  works as expected ***
     a.push( function() {return i;} );
   }
} ());
console.log(a.map( function(f) {return f();} ));
// prints [0, 1, 2, 3, 4]

// Start over, but change `let` to `var`.
// prints [5, 5, 5, 5, 5]
eridal commented 4 years ago

Here is a counter-example, to show why var should not be used:

  1. This will print an error, as the var is not defined

    function varCheck() {
    console.log(x) // => Uncaught ReferenceError: x is not defined
    }
  2. but this will silently print undefined, even if the var might seem not defined

function varCheck() {
  console.log(x) // => prints undefined
  var x
}