23 June, 2020

💾 Overview of Javascript fundamentals - part 1

Why are functions called First-class Objects?

In JS, functions are first-class objects because they're treated as any other value.

For example, you can assign functions to variables, array elements, and properties of objects. They can also be passed around as arguments to other functions or be returned from those functions.

The only difference between a function and any other value in JavaScript is that functions can be invoked or called.

Handling asynchronous calls with callbacks, promises, await, and async

JavaScript is single threaded. All code is executed in a sequence, not in parallel. This is handled by using what is called an "asynchronous non-blocking I/O model"

What that means is that while the execution of JavaScript is blocking, I/O operations are not. example of I/O operations could be fetching data over the internet with Ajax or over WebSocket connections, querying data from a database or accessing the filesystem with the NodeJs "fs" module. All these kind of operations are done in parallel to the execution of your code and it's not JavaScript that does these operations; the underlying engine does it.

For JavaScript to know when an asynchronous operation has a result (a result being either returned data or an error that occurred during the operation), it points to a function that will be executed once that result is ready. This function is what we call a "callback function". Meanwhile, JavaScript continues its normal execution of code.

const request = require(‘request’);
function handleResponse(error, response, body){
    if(error){
        // Handle error.
    }
    else {
        // Successful, do something with the result.
    }
}
request('https://www.somepage.com', handleResponse);

As you can see, "request" takes a function as its last argument. This function is not executed together with the code above. It is saved to be executed later once the underlying I/O operation of fetching data over HTTP(s) is done. The underlying HTTP(s) request is an asynchronous operation and does not block the rest of the JavaScript code's execution. The callback function is put on a sort of queue called the "event loop." until the function will be executed with a result from the request. Callbacks are a good way to declare what will happen once an I/O operation has a result, but what if you want to use that data in order to make another request? You can only handle the result of the request (if we use the example above) within the callback function provided.

One thing to note here is the first argument in every callback function will contain an error if something went wrong, or will be empty if all went well. This pattern is called "error first callbacks" and is very common. It is the standard pattern for callback-based APIs in NodeJs.

What's a promise?

A promise is an object that wraps an asynchronous operation and notifies when it's done. The Promise may produce a single value some time in the future. It could return a resolved value, or a reason why it couldn't; such as a network error. A promise may be in 1 of 3 possible states: "fulfilled, rejected, or pending ".

Promises execute immediately. They're eager, meaning that a promise will start doing whatever task you give it as soon as the promise constructor is invoked.

The process of wrapping a callback-based asynchronous function inside a Promise and return that Promise instead is called "promisification". We are "promisifying" a callback-based function.

If you need lazy, check then use observables or tasks. The true power of promises is shown when you have several asynchronous operations that depend on each other,

Another important thing to notice is that even though we are doing two different asynchronous requests we only have one .catch() where we handle our errors. That's because any error that occurs in the Promise chain will stop further execution, and an error will end up in the next .catch() in the chain

Example js promise

Every call to then or catch returns a new promise. Rejections(errors) pass through promises until they reach a catch() async/await is syntactic sugar on top of promises.

Async is a keyword that marks a function as returning a Promise. Any return will be wrapped in a resolved promise, and any error thrown will instead return a rejected promise. An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and evaluates as the resolved value. asyn function pre refactor asyn function

I stated before that you only need one .catch(..) at the end of a Promise chain even though you are doing several asynchronous calls in that chain. The same goes for async/await and error handling with try/catch. You only need to surround the code in the "first" async function with try-catch. That function can await one or more other async functions which in return does their own asynchronous calls by awaiting one or more other async functions etc.

further: you can use Promise.all for parallelization. note: you cannot use await in top-level JavaScript code

A gotcha.

js>forEach won't work with promises

The issue is that the callback is called but it won't wait for it to be done before going to the next entry of the array.

async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

What's the difference between call, apply and bind

If an object doesnt have a function but you want to call that function on that object.

"call" is a method on every function that allows you to invoke the function specifying in what context the function will be invoked. for example

greet.call(user)

where greet is a function and user is an object.

Bind creates a new function that will have this set to the first parameter passed to bind().
The call, apply methods invokes the function immediately instead of returning a new function.

The only difference between apply and call is how we pass the arguments in the function being called. In apply we pass the arguments as an array and in call, we pass the arguments directly in the argument list.

source: https://tylermcginnis.com/this-keyword-call-apply-bind-javascript/

Bind() is useful for maintaining context in asynchronous callbacks and events.

const obj1 = {
 result:0
};

const obj2 = {
 result:0
};

function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}

reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // returns 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15

.call as well as .apply which both allow you to invoke a function, specifying what the this keyword is going to be referencing inside of that function.

What the difference between let and var?

Variables declared with var keyword are function scoped. What this means is that variables can be accessed across that function even if we declare that variable inside a block. variables are hoisted at the top of the function scope.

Variables declared with let and const keyword are block scoped. Meaning that variable can only be accessed on that block {} on where we declare it.

unaccessible variables using var will return undefined. with let/const it'll return reference error.

Diff between map and foreach

map allocates memory and stores return values. forEach throws away return values and always returns undefined. forEach will allow a callback function to mutate the current array. map will instead return a new array.

.map, .filter, .reduce and .find all behave very similarly to .forEach

let ages = data
  .filter((animal) => {
    return animal.type === 'dog';
}).map((animal) => {
    return animal.age * 7
}).reduce((sum, animal) => {
    return sum + animal.age;
});
// ages = 84

this can be refactored to:

let ages = data
  .filter(isDog)
  .map(dogYears)
  .reduce(sum);
// ages = 84

Explain event delegation

A technique involving adding event listeners to a parent element instead of adding them to the descendant elements.
The listener will fire whenever the event is triggered on the descendant elements due to event bubbling up the DOM.
Benifits:

  • Memory footprint goes down because only one single handler is needed on the parent element, rather than having to attach event handlers on each descendant.
  • There is no need to unbind the handler from elements that are removed and to bind the event for new elements.

JS architecture and paradigms

JavaScript is a multi-paradigm language, supporting imperative/procedural programming along with OOP (Object-Oriented Programming) and functional programming.
JavaScript supports OOP with prototypal inheritance.

For the record: Functional programming has mostly won over class inheritance in JavaScript. Libraries and frameworks like React, Redux, RxJS, Lodash, and Ramda dominate over inheritance-based libraries and frameworks. All of them favor functional programming over class-based monolithic code.

Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. In other words, Programs that describe their desired results without explicitly listing commands or steps that must be performed. Functional and logical programming languages are characterized by a declarative programming style.

Imperative programming is a programming paradigm that uses statements that change a program's state. Functional programming is characterized by functions that calculate a value without changing the functions arguments that are passed to it. in other words pure functions that avoid side effects.

JavaScript supports Closures and Higher Order Functions which are a characteristic of a Functional Programming Language. In addition, Array has map, filter and reduce methods which are the most famous functions in the functional programming world as they don't mutate or change the array making these functions pure.

OOP and functional programming both have their disadvntages that need to be weighed out. A highly OOP codebase can be extremely resistant to change and very brittle
and highly functional codebase can have a steep learning curve.