2

I'm reading through this article on the inner workings of a promise. To do that, the author showcases a simplified implementation of a promise working properly.

The code is as follows:

class PromiseSimple {
  constructor(executionFunction) {
    this.promiseChain = [];
    this.handleError = () => {};

    this.onResolve = this.onResolve.bind(this);
    this.onReject = this.onReject.bind(this);

    executionFunction(this.onResolve, this.onReject);
  }

  then(onResolve) {
    this.promiseChain.push(onResolve);

    return this;
  }

  catch(handleError) {
    this.handleError = handleError;

    return this;
  }

  onResolve(value) {
    let storedValue = value;

    try {
      this.promiseChain.forEach((nextFunction) => {
         storedValue = nextFunction(storedValue);
      });
    } catch (error) {
      this.promiseChain = [];

      this.onReject(error);
    }
  }

  onReject(error) {
    this.handleError(error);
  }
}

And it is called as such, just like a regular promise:

// Assume this is your AJAX library. Almost all newer
// ones return a Promise Object
const makeApiCall = () => {
  return new PromiseSimple((resolve, reject) => {
    // Use a timeout to simulate the network delay waiting for the response.
    // This is THE reason you use a promise. It waits for the API to respond
    // and after received, it executes code in the `then()` blocks in order.
    // If it executed is immediately, there would be no data.
    setTimeout(() => {
      const apiResponse = fakeApiBackend();

      if (apiResponse.statusCode >= 400) {
        reject(apiResponse);
      } else {
        resolve(apiResponse.data);
      }
    }, 5000);
  });
};

I'm having a hard time grasping the following:

  1. Why do lines 6 and 7 of the PromiseSimple class exist? I'm trying to understand the point of binding this.onResolve to this. Isn't it already bound to the proper this context?

  2. I don't understand why this implementation doesn't block the main thread. No where in the PromiseSimple class does it offload the work onto another thread or anything of the sort. But surely enough, if I put a console.log(...) statement at the very end of my program, that console.log(...) statement would be printed out as the first thing I'd expect it to, like a regular promise. I figured it'd be paused until the fake makeApiCall function had finished since this isn't a true implementation of a promise.

I want to grasp this but I'm just not getting how this mini implementation allows the proper promise behavior that we're used to. Any help is greatly appreciated.


Update

A recommendation of making this a duplicate has been proposed but I want to elaborate why they are not the same. The duplicate question is more high level theory of why async calls are needed. I understand their use, and importance, and the various ways they're implemented. I'm looking to understand under-the-hood how they work.

qarthandso
  • 2,100
  • 2
  • 24
  • 40
  • Javascript promises is none blocking by default. It doesn't block the thread because promises are asynchonous, that's their point, to prevent blocking. – Liam Aug 17 '18 at 13:52
  • 1
    Author warns "NOTE: This version of a Promise is for educational purposes only. I’ve left out some of the more advanced features and distilled it to its core functionality". IMHO, he's left out some of the essential features too. – Roamer-1888 Aug 17 '18 at 13:53
  • 1
    That article seems nonsense. Just use promises, there is no need to build a "simple promise". – Liam Aug 17 '18 at 13:54
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Liam Aug 17 '18 at 13:54
  • Thank you and I am reading the possible duplicate. My main question is, why doesn't `executionFunction()` in the `constructor` block the rest of the script from running? What's the difference placing it in this `PromiseSimple` class? – qarthandso Aug 17 '18 at 14:08
  • 2
    @qarthandso, the proposed duplicate is specious. Ignore it. – Roamer-1888 Aug 17 '18 at 14:12
  • Promises are a language feature of Javascript that implement callbacks after asynchronous code has been called. So you call x and say when the returns run this code. The (single) ui thread then moves onto the next call. There is no Promise implementation because the browser (or node) handles this not Javascript itself – Liam Aug 17 '18 at 14:14
  • If you want to know about the specification of how promises should work this is covered by the ECMA specification here: https://tc39.github.io/ecma262/#sec-promise-objects – Liam Aug 17 '18 at 14:16
  • 2
    This is the most horrible "promise" implementation I've ever seen. If you want to actually understand the idea behind a promise implementation, I recommend [this answer](https://stackoverflow.com/a/17724387/1048572) and [Distilling how a promise works?](https://stackoverflow.com/q/15668075/1048572) – Bergi Aug 17 '18 at 14:21
  • @Liam No, the spec doesn't really help *understanding* how promises work. It's much too convoluted and detailed. – Bergi Aug 17 '18 at 14:21
  • I was trying to address the updated question *I'm looking to understand under-the-hood how they work* @Bergi – Liam Aug 17 '18 at 14:22
  • 2
    "*don't understand why this implementation doesn't block the main thread*" - that's a common misunderstanding. Promises do not "*offload the work onto another thread or anything of the sort*", that's not what they are supposed to do. They are just a very useful abstraction over something that is *already asynchronous* - in your example, that's the `setTimeout`. – Bergi Aug 17 '18 at 14:24
  • I like the analogy, You call someone and ask them for some information, async is that person saying they'll call you back once they have the information, in the mean time you do something else. – Liam Aug 17 '18 at 14:26
  • 1
    @Bergi this is extremely key. Thank you. I felt that the `promise` was doing something to allow execution to continue, but it's not. So in my example it's actually **`setTimeout`** that has something under the hood that offloads it off the current execution context or thread? If we're doing an AJAX call, then I'm guessing `XMLHttpRequest` also has something under the hood that allows it to offload itself until a response is received? – qarthandso Aug 17 '18 at 14:33
  • 1
    @qarthandso Yes, they offload the waiting and the http request to some background processing, and when they are ready the schedule a callback on the JS "main" thread. You might want to learn about the *event loop*, the major idea behind JavaScript's asynchrony. – Bergi Aug 17 '18 at 14:37
  • 1
    @Bergi That's what I'll tackle next. There's really no "magic" of promises. It's just a great way to abstract the asynchrony that's already implemented. – qarthandso Aug 17 '18 at 14:41

2 Answers2

2
  1. onResolve and onReject have to be bound in order to prevent the executionFunction to apply them with another context or without any context. (if for whatever reason you call resolve or reject with another context, it has to be bound to PromiseSimple, otherwize this will refer to something else or won't refer to anything if no context).

Here is an example that won't work if you don't bind onResolve or onReject :

const makeApiCall = () => {
    return new PromiseSimple((resolve, reject) => {
        Promise.resolve().then(resolve); // Using native promise here : resolve is called without context and won't work if not bound
    });
}
  1. This implementation does block the main thread but you won't probably see it since you're using a setTimeout which delays the execution in the event loop. The true Promise implementation delays the tasks you define in then or catch callback as microtask in the event loop. (There is this very interresting article about browsers event loop).

Hope this helps,

Community
  • 1
  • 1
Samuel Maisonneuve
  • 1,025
  • 10
  • 21
  • Thanks a lot @Samuel. The article link is appreciated. I'm not sure I understand when someone may want to bind `resolve` or `reject` to another context, or what purpose that would serve. Is there any scenario that would help me to better understand why someone would ever reassign the context of `resolve` or `reject`? – qarthandso Aug 17 '18 at 15:56
  • Hi, i added a small example : If not bound, resolve or reject could also be called without context.. – Samuel Maisonneuve Aug 17 '18 at 16:07
  • Thanks a lot @Samuel. Really appreciate. – qarthandso Aug 17 '18 at 16:19
-1

regarding 1, let's look at a standalone example

execute = func  => func()
class X {
    xfunc() {return this;}
}
let x = new X();
execute(x.xfunc) // undefined
execute(x.xfunc.bind(x)) // x
execute(x.xfunc.bind(5)) // 5

in general, when you refer to a function, such as x.xfunc it loses it's this context. This isn't usually a problem, because you usually don't invoke object functions like this, but rather call x.xfunc() directly. Same goes to all objects, not just objects created with the class and new syntax.

let x = {xfunc: function() {return this}}
x.xfunc() // x
execute(x.xfunc) // window
execute(x.xfunc.bind(x)) // x

Now, with arrow functions, it's a bit different

An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target MDN

let x = {xfunc: () => this}
x.xfunc() // window
execute(x.xfunc) // window
execute(x.xfunc.bind(x)) // window

Regarding 2, it's essentially just callbacks. Consider the below example

let callback = () => { console.log('callback') }
let promise = { resolve: () => { callback() } }
console.log('this is not blocking');

Now, it should be obvious that nothing is blocking, e.g. the console log is printed even though we never resolved the promise via promise.resolve(). Promises r really just syntactical sugar for the above with some extra logic for chaining.

junvar
  • 11,151
  • 2
  • 30
  • 46
  • 1
    never said they were related. the question was asking for clarification on binding `this`. – junvar Aug 17 '18 at 14:20
  • 1
    `I'm trying to understand the point of binding this.onResolve to this. Isn't it already bound to the proper this context?` – junvar Aug 17 '18 at 14:25
  • Ok, so I'll maybe give you that you have addressed one of the points, but the main thrust is Promises. This is the problem with OPs asking multiple questions. Does this answer the whole question no. Does it answer some of the question, well kinda I suppose. – Liam Aug 17 '18 at 14:29