Promise object

The title content
Source of Promise Why do I need a Promise object?
Principle of Promise How does Promise come true?
Promise to realize How do I implement promises natively?

Why do I need a Promise object?

  • Before we dive into this, we need to take a look at asynchronous programming.

Asynchronous programming

  • First of all, we all know that the JavaScript language is executed in”Single thread“(single thread).
  • Single-threaded means that you can only complete one task at a time. If there are multiple tasks, they must be queued, the first task completed, the next task executed, and so on. For example:
  • The handsome man/beautiful woman who read the article will have such trouble, is the long queue of suitors, but the handsome man/beautiful woman for their emotional history and need to be stable, simple, so the use of a single thread, to the outside world to open a pursuit of the door, can only wait for a suitor was rejected, the next suitor can enter.
/ * * *@description If wooer1 is chatting for a long time (10 sentences), wooer2 will wait * if wooer1 is finished (rejected), wooer2 enters and the chat ends for a short time * wooer3 enters */
function wooer1() {
  var i = 0;
  while(i < 10) {
    console.log("Say Hello!"); i++; }}function wooer2() {
  console.log("Say Hello!");
}

function wooer3() {
  console.log("Say Hello!");
}

wooer1();
wooer2();
wooer3();
Copy the code

Benefits of single threading

  • The implementation is relatively simple, the execution environment is relatively simple and stable;

Disadvantages of single threading

  • As long as one task takes a long time, subsequent tasks must wait in line, delaying the execution of the entire program. A common browser non-response (false death) is usually caused by a piece of JavaScript code running for a long time (such as an infinite loop, remember: always avoid infinite loops in Web development) so that the entire page gets stuck in one place and no other tasks can be performed.
/ * * *@description 2 will never output, test code, please do not write to the program * */
while(true) {
  console.log(1);
}
console.log(2);
Copy the code

JavaScript execution patterns (Bonus points)

  • The JavaScript language divides the execution modes of tasks into two types:synchronous(Synchronous) andasynchronous(Asynchronous).

Synchronous mode of JavaScript execution mode

  • “Synchronous mode” is the mode of single thread, the last task wait for the end of the previous task, and then execute, the execution order of the program is consistent with the task order, synchronous;
function f1() {
  var i = 0;
  while(i < 10) {
    console.log(i); i++; }}function f2() {
  console.log(13);
}

f1(); // f2 will be executed only after f1 is completed
f2();
Copy the code

Asynchronous JavaScript execution mode

  • Asynchronous mode“It’s totally different. Every taskThere is one or moreCallback function (callback), after the end of the former task, not to perform the latter task, butExecute the callback functionThe latter task is executed without waiting for the end of the former task, soThe order in which a program is executedwithOrder of tasksisinconsistent,asynchronous.
  • Using asynchronous mode (AsynchronousWrite the function method, i.eAsynchronous programming.

There was no asynchronous programming approach as before Promise

The callback function

  • This is the most basic approach to asynchronous programming.
/ * * *@description Suppose you have two functions f1 and f2, the latter waiting for the result of the former. * If f1 is a time-consuming task, consider rewriting f1 to write f2 as a callback function of F1. * /
function f1() {
  console.log(1);
}

function f2() {
  console.log(2);
}

f1();
f2();
Copy the code
/ * * *@function F1 defines the main function with the callback function as argument *@function F2 defines the callback function */
function f1(callback) {
  setTimeout(function () { // imitate the function operation
    // the task code for f1
    callback();
  }, 1000);
  console.log(1);
}

function f2() {
  console.log(2);
}
f1(f2);
/ / 1
/ / 2
Copy the code
/ * * *@function F1 defines the main function with the callback function as argument *@function F2 defines the callback function */
function f1(callback) {
  callback();
  console.log('f1() I'm the main function! ');
}

function f2() {
  setTimeout(function () {
    console.log('f2() I'm a callback! ');
  }, 30000); // Imitate time-consuming operations
}

f1(f2);
// f1() I am the main function
// f2() I am the callback function
Copy the code
  • We have changed the synchronous operation to asynchronous operation. F1 does not block the program running, which is equivalent to executing the main logic of the program first and postponing the execution of time-consuming operation.

  • Advantages: Simple, easy to understand and deploy.

  • Disadvantages: Code is not easy to read and maintain, parts are highly coupled, the process can be confusing, and only one callback function can be specified for each task.

Event listeners

  • Another approach is to adopt an event-driven model. The execution of tasks depends not on the order of the code, but on whether an event occurs.
/ * * *@description https://code.jquery.com/jquery-3.6.0.js * source address@function F1 binds an event *@function F2 When the done event occurs in f1, run f2. * /
function f2() {
  console.log('2');
}

f1.on('done', f2);

function f1() {
  setTimeout(function () {
    f1.trigger('done'); // Immediately after the execution is complete, the done event is triggered to start the f2 execution.
  }, 1000);
}
Copy the code
  • Advantages: easy to understand, can bind multiple events, each event can specify multiple callback functions, and “decouple”, good for modularity.
  • Disadvantages: The entire application becomes event-driven, and the flow becomes unclear.

Publish/subscribe

  • An event driven event can be understood as a signal.
  • We assume that there is one.”The signal center“, a task completed, toThe signal center “publishes” a signal, other tasks can be directed toThe signal center “subscribes” to this signalTo know when you can start executing. This is called”Publish/subscribe“(publish-subscribe pattern), also called”Observer model“(observer pattern).
var publisher = {
  subscribers: {
    any: []},subscribe: function (fn, type=`any`) {
    if (typeof this.subscribers[type] === `undefined`) {
      this.subscribers[type] = [];
    }
    this.subscribers[type].push(fn);
  },
  unSubscribe: function (fn, type=`any`) {
    var newSubscribers = [];
    this.subscribers[type].forEach((item, i) = > {
      if (item !== fn) {
        newSubscribers.push(fn);
      }
    });
    this.subscribers[type] = newSubscribers;
  },
  publish: function (args, type=`any`) {
    this.subscribers[type].forEach((item, i) = >{ item(args); }); }};function makePublisher(obj) {
  for (var i in publisher) {
    if (publisher.hasOwnProperty(i) && typeof publisher[i] === `function`) {
      obj[i] = publisher[i];
    }
  }
  obj.subscribers = { any: []}; }var paper = {
  day: function () {
    this.publish(`today! `);
  },
  week: function () {
    this.publish(`a week has seven days! `.`week`); }}; makePublisher(paper);var test = {
  sayTime: function(paper) {
    console.log(`Today is ` + paper);
  },
  sayDate: function(week) {
    console.log(`About to fall asleep reading this `+ week); }}; paper.subscribe(test.sayTime); paper.subscribe(test.sayDate,`week`);

paper.day(); // Today is today!
paper.day(); // Today is today!
paper.week(); // About to fall asleep reading this a week has seven days
paper.week(); // About to fall asleep reading this a week has seven days
paper.week(); // About to fall asleep reading this a week has seven days
Copy the code
/ * * *@description Source address https://code.jquery.com/jquery-3.6.0.js * first, f2 to "signal center" jQuery subscription "done" signal. * /
function f2() {
  console.log(2);
}

jQuery.subscribe("done", f2);

// Then, f1 is written as follows:
function f1() {
  setTimeout(function() {
    // the task code for f1
    jQuery.publish("done");
  }, 3000)};// jquery.publish ("done") means that when f1 is finished, the "done" signal is issued to the "signal center "jQuery, which causes f2 to execute.
// In addition, f2 can also be unsubscribed after completion of execution.
jQuery.unsubscribe("done", f2);
Copy the code
/ * * *@description * /
const button = document.querySelector("button");
button.addEventListener("click".(event) = > /* do something with the evnet */);
Copy the code
/ * * *@description * /
var n = 0;
const evnet = new EventEmitter();

event.subscribe("THUNDER_ON_THE_MOUNTAIN".value= > (n = value));

event.emit("THUNDER_ON_THE_MOUNTAIN".18);

// n: 18

event.emit("THUNDER_ON_THE_MOUNTAIN".5);

// n: 5
Copy the code
  • The publish/subscribe approach is similar in nature to, but significantly superior to, event listening. Because we can monitor the program by looking at the “message center” to see how many signals exist and how many subscribers each signal has.

  • These are several ways of asynchronous programming and implementation of asynchronous programming, but there are more or less imperfect places in the way, so in ES6 introduced Promise(a solution to asynchronous programming), because Promise is more reasonable and more powerful, then why Promise is more reasonable and powerful?


Principle of Promise

  • PromiseA solution to asynchronous programmingThan the traditional solutionCallback functions and eventsA more reasonableandMore powerful.
  • The so-calledPromiseIn short, it is oneThe containerIt is preserved insideSomething that will end in the future(usually an asynchronous operation).
  • Grammatically speaking,PromiseIs an object from which to get messages for asynchronous operations.
  • PromiseA unified API is provided so that various asynchronous operations can be handled in the same way.
/ * * *@description The Promise constructor takes a function as an argument, resolve and reject. Resolve and reject are two functions that are provided by the JavaScript engine and do not need to be deployed yourself. *@function The Resolve JavaScript engine provides *@function Reject JavaScript engine provides */
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else{ reject(error); }});Copy the code

Characteristics of Promise

1. The status of the object is not affected.

  • PromiseThe object represents an asynchronous operation with three states:pending(In progress),fulfilled(resolved(Successfully) andrejected(Failed). Only the result of an asynchronous operation can determine which state it is in,Nothing else can change this state. That’s where the name “Promise” comes from. It means “Promise” in EnglishNothing else will change it(This, of course, is itself a limitation).
/ * * *@function resolveResolve Changes the state of the Promise object from "unfinished" to "successful "(pending -> Resolved). Resolve is called when the asynchronous operation succeeds and passes the result of the asynchronous operation as an argument. * *@function rejectThe * reject function changes the state of the Promise object from "reject" to "fail "(pending -> Rejected) * when the asynchronous operation fails and passes the result of the asynchronous operation as an argument. * /
const promise1 = new Promise(function(resolve, reject) {
  // ... some code
  var value = 1;
  if (value === 1) {
    resolve(value);
  } else{ reject(value); }});/ * * *@description After the Promise strength is generated, you can use the THEN method to specify the resolved and Rejected state callback functions, respectively. The * then method can take two callback functions as arguments. * The first callback argument: called when the state of the Promise object becomes Resolved. * The second callback argument: called when the state of the Promise object changes to Rejected. * Both parameters are optional and do not have to be supplied. * Both callbacks accept a value from the Promise object as an argument. * /
promise1.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code

2. Once the state changes, it will never change again. This result can be obtained at any time.PromiseThe state of an object can change in only two ways: frompendingintofulfilledAnd from thependingintorejected.

  • As soon as those two things happen,The state solidifiesIt’s not going to change anymore, it’s going to stay the sameresolved(Have to finalize the design).
  • If the change has already happened, then you are rightPromiseObject to addThe callback function, also canGet the result immediately.
  • This is related to events (Event) Completely different. The feature of the event is that if you miss it, listen again. Yesfruitless.
/ * * *@description An asynchronous image loading operation is wrapped with Promise. * If the load succeeds, the resolve method is called, otherwise the Reject method is called. * /
function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    const img = new Image();

    img.onload = function () {
      resolve(img);
    }

    img.onerror = function () {
      reject(new Error('Could not load image at '+ url)); } img.src = url; })}Copy the code
const promise2 = new Promise(function(resolve, reject) {
  var value = 2;
  if (value === 2) {
    resolve(value);
  } else{ reject(value); }});Copy the code

The benefits of the Promise

  • Asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions.
  • PromiseObject providesUnified interface, so that the controlAsynchronous operations are easier.

The shortcoming of Promise

  • There is no way to cancel a Promise. Once a new Promise is created, it is executed immediately.
  • Without a callback function, the Promise throws an error internally and does not react externally.
  • When inpendingIn state, there is no way to know the current progress stage (Just beginning or just finishing).

How to fulfill a Promise

var _MyPromise = function (resolver) {
  var _this = this
  _this._status = 'pending'
  _this._result = ' '
  resolver(_this.resolve.bind(_this), _this.reject.bind(_this))
}

_MyPromise.prototype.resolve = function (result) {
  var _this = this
  if (_this._status === 'pending') {
    _this._status = 'fullfilled'
    _this._result = result
  }
  return _this
}

_MyPromise.prototype.reject = function (reject) {
  var _this = this
  if (_this._status === 'pending') {
    _this._status = 'rejected'
    _this._result = result
  }
  return _this
}

_MyPromise.prototype.then = function (isResolve, isReject) {
  var _this = this
  if (_this._status === 'fullfilled') {
    var _isPromise = isResolve(_this._result)
    if (_isPromise instanceof _MyPromise) {
      return _isPromise(_this._result)
    }
    return _this
  } else if (_this._status === 'rejected' && arguments[1]) {
    var _err = TypeError(_this._result)
    var _isPromise = isReject(_err)
    if (_isPromise instanceof _MyPromise) {
      return _isPromise(_err)
    }
    return _this
  }
}

_MyPromise.prototype.catch = function (isReject) {
  var _this = this
  if (_this._status === 'rejected') {
    var _err = TypeError(_this._result)
    var _isPromise = isReject(_err)
    if (_isPromise instanceof _MyPromise) {
      return isPromise(_err)
    }
    return _this
  }
}

var promise1 = new _MyPromise(function (resolve, reject) {
  var a = 10
  resolve(10)})Copy the code

JackDan Thinking

Developer.mozilla.org/zh-CN/docs/…

Es6.ruanyifeng.com/#docs/promi…