preface

During the interview, the interviewer will often ask you to realize A Promise, and if you follow the A+ specification to realize it, it may not end until dark.

When we talk about promises, the core functionality that comes to mind is asynchronous chained calls. This article takes you to implement a Promise that can be asynchronously chained in 20 lines of code.

The Promise was implemented with no exceptions, except for the briefest possible code, so that the reader could understand the core principle of asynchronous chained calls.

code

Give me the code first. It’s really 20 lines.

function Promise(fn) {
  this.cbs = [];

  const resolve = (value) = > {
    setTimeout((a)= > {
      this.data = value;
      this.cbs.forEach((cb) = > cb(value));
    });
  }

  fn(resolve);
}

Promise.prototype.then = function (onResolved) {
  return new Promise((resolve) = > {
    this.cbs.push((a)= > {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        res.then(resolve);
      } else{ resolve(res); }}); }); };Copy the code

The core of case

new Promise((resolve) = > {
  setTimeout((a)= > {
    resolve(1);
  }, 500);
})
  .then((res) = > {
    console.log(res);
    return new Promise((resolve) = > {
      setTimeout((a)= > {
        resolve(2);
      }, 500);
    });
  })
  .then(console.log);
Copy the code

This article will focus on the core case, and this code behaves as follows:

  1. Output 1 after 500ms
  2. Output 2 after 500ms

implementation

The constructor

First, implement the Promise constructor

function Promise(fn) {
  // The set of callbacks for Promise resolve
  this.cbs = [];

  // Pass the resolve to the Promise handler
  // Insert data directly into the instance
  // Then execute the onResolvedCallback array one by one
  const resolve = (value) = > {
    // Note that promise's then function needs to be executed asynchronously
    setTimeout((a)= > {
      this.data = value;
      this.cbs.forEach((cb) = > cb(value));
    });
  }

  // Execute the function passed in by the user
  // And give the resolve method to the user
  fn(resolve);
}
Copy the code

All right, so let’s go back to the case here

const fn = (resolve) = > {
  setTimeout((a)= > {
    resolve(1);
  }, 500);
};

new Promise(fn);
Copy the code

Separately, fn is the user-passed function that executes all CBS on the PROMISE instance when it calls resolve internally.

At this point we don’t know where the CBS array of functions came from, so let’s move on.

then

Here’s the most important then implementation that chaining calls depend on:

Promise.prototype.then = function (onResolved) {
  // This is called promise2
  return new Promise((resolve) = > {
    this.cbs.push((a)= > {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        // The power to resolve is given to the user Promise
        res.then(resolve);
      } else {
        // If the value is a normal value, go to resolve
        // Execute the functions in CBS in turn and pass the values to CBSresolve(res); }}); }); };Copy the code

Let’s go back to the case

const fn = (resolve) = > {
  setTimeout((a)= > {
    resolve(1);
  }, 500);
};

const promise1 = new Promise(fn);

promise1.then((res) = > {
  console.log(res);
  // user promise
  return new Promise((resolve) = > {
    setTimeout((a)= > {
      resolve(2);
    }, 500);
  });
});
Copy the code

Notice the naming here:

  1. We call the instance returned by new Promise “promisE1”

  2. In the promise.prototype. then implementation, we construct a new Promise return called Promise2

  3. When the user calls the THEN method, the user manually constructs a promise and returns it for an asynchronous operation, called the User Promise

In the then implementation, the internal “this” actually points to “promisE1”

For example, if this function is passed to promise2, it will execute this.cbs.push(). For example, if this function is passed to Promise2, it will execute this.

Promise.prototype.then = function (onResolved) {
  // This is called promise2
  return new Promise((resolve) = > {
    // This is actually promise1
    this.cbs.push((a)= > {});
  });
};
Copy the code

This function is not implemented until promise1 is resolved.

// promise2
return new Promise((resolve) = > {
  this.cbs.push((a)= > {
    // onResolved corresponds to the function passed in then
    const res = onResolved(this.data)
    // In this case, the user returns a User Promise
    if (res instanceof Promise) {
      // This is the case for user Promise
      // The user decides when to resolve Promise2
      // Only after promise2 is resolved
      // Then the following chained call function will continue
      res.then(resolve)
    } else {
      resolve(res)
    }
  })
})
Copy the code

If the onResolved method the user passes to then returns a User promise, the user will resolve promise2 at the appropriate time. Then the resolve in res.then(resolve) will be executed:

if (res instanceof Promise) {
    res.then(resolve)
}
Copy the code

Consider this example:

new Promise((resolve) = > {
  setTimeout((a)= > {
    // resolve1
    resolve(1);
  }, 500);
})
  // then1
  .then((res) = > {
    console.log(res);
    // user promise
    return new Promise((resolve) = > {
      setTimeout((a)= > {
        // resolve2
        resolve(2);
      }, 500);
    });
  })
  // then2
  .then(console.log);
Copy the code

Then2 is essentially promise2.then(console.log),

Then2: resolve (); then2: resolve (); then2: resolve (); In turn, the CBS array in Promise2 is triggered.

This makes it possible for the logic in then2 to continue executing after the user-written resolve2 completes, which is an asynchronous chain-call.

The article summarizes

This article simply implements a promise that can be called asynchronously, but real Promises are much, much more complex and involve handling various exception cases and boundary cases.

The Promise A+ specification is still worth reading by every qualified front-end developer.

Hope this article is helpful to you!