preface

Understanding what promises are and what they do is definitely part of the way we learn JavaScript.

What is Promise?

A Promise is a solution to asynchronous programming. Syntactically, it is an object that represents the completion or failure of an asynchronous operation. Semantically, it is a Promise to give you an outcome over time. Promise three clock state, respectively is pending, fulfiled, rejected.

Since its prototype then,catch,finally returns a new promise, it allows us to make chain calls, solving the problem of traditional callback hell.

Because it has an all method, it can support multiple concurrent requests and retrieve data from concurrent requests.

Promise features

  1. Once a promise status changes, it cannot be changed.
const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});
promise.then(res => {
    console.log("then: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  })
Copy the code

Results:

"then: success1"
Copy the code

The resolve or reject constructor is valid only on the first execution; multiple calls do nothing.

  1. inPromise, returns any notpromiseIs wrapped around the value ofpromiseObject, for examplereturn 2Will be packaged asreturn Promise.resolve(2).
Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
Copy the code

Results:

1
2
Copy the code

Return 2 will be wrapped as resolve(2)

Then, catch method

.then vs..catch

The then method takes two arguments: the first is the function that handled the success and the second is the function that failed. The second argument to the then method is, to some extent, a shorthand for. Catch.

Promise.reject('err!!! ') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) })Copy the code

The execution result here is:

'error' 'error!!! 'Copy the code

It goes into the second argument in then(), and if you remove the second argument, it goes into catch()

Promise.reject('error!!! ') .then((res) => { console.log('success', res) }).catch(err => { console.log('catch', err) })Copy the code

Execution Result:

catch' 'error!!! 'Copy the code

If it’s the following

Promise.resolve() .then(function success (res) { throw new Error('error!!! ') }, function fail1 (err) { console.log('fail1', err) }).catch(function fail2 (err) { console.log('fail2', err) })Copy the code

Since the Promise calls resolve(),.then() should execute success(), but success() throws an error that is caught by a catch(), not by fail1.

Therefore, the execution result is:

fail2 Error: error!!!
Copy the code

2. The callback function catch, which is used to execute failed requests, catches errors that cannot be caught by the upper layer no matter where the connection is located.

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })
Copy the code

Results:

"catch: " "error"
"then3: " undefined
Copy the code

A catch catches errors that have not been caught by the upper layer, regardless of where it is connected.

Then3 is also executed because catch() also returns a Promise, and since the Promise returns no value, it prints undefined.

.catch and.then

Catch and. Then both return a new promise

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
Copy the code

The results of

'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
Copy the code

An error object does not throw an error, so it will not be caught by a later catch

Promise.resolve().then(() => { return new Error('error!!! ') }).then(res => { console.log("then: ", res) }).catch(err => { console.log("catch: ", err) })Copy the code

Guess what the output here is 🤔️?

You might want to go into.catch and get caught with an error.

It doesn’t turn out that way, it goes in dot then

"then: " "Error: error!!!"
Copy the code

Return new Error(‘ Error!!! Resolve (new Error(‘ Error!!! ‘)).

Of course, if you throw an error, you can use either of the following 👇 :

return Promise.reject(new Error('error!!! ')); // or throw new Error('error!!! ')Copy the code

3. Then and. Catch cannot return the promise that called them, otherwise an infinite loop will occur

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
Copy the code

The value returned by. Then or. Catch cannot be the promise itself, otherwise an infinite loop will occur.

Uncaught (in promise) TypeError: Chaining cycle detected for promise #
Copy the code

4. The arguments to then and catch are expected to be functions, and value pass-through will occur if passed to non-functions

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
Copy the code

The value of resolve(1) is passed directly to the last THEN, because the first and second THEN are not functions; one is a number and the other is an object.

So the output is:

1
Copy the code

5. The.then and.catch of a promise can be called multiple times, but if the state of the promise changes and a value is given, each subsequent call to the.then and.catch will fetch that value directly.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    resolve('success')
  }, 1000)
})
const start = Date.now();
promise.then(res => {
  console.log(res, Date.now() - start)
})
promise.then(res => {
  console.log(res, Date.now() - start)
})
Copy the code

Execution Result:

'timer'
'success' 1001
'success' 1002
Copy the code

Of course, if you’re fast enough, it could be 1001 for both.

A Promise’s.then or.catch can be called multiple times, but here the Promise constructor is executed only once. Or once the internal state of a promise changes and it has a value, each subsequent call to.then or.catch will get that value directly.

Method. The finally

  1. The.finally method executes regardless of whether the final state of the Promise object succeeds or fails.
Promise.resolve('1')
   .then(res => {
   console.log(res)
   })
   .finally(() => {
   console.log('finally')
   })
Copy the code

The results of

`'1'`
`'finally'`
Copy the code
  1. The.finally method does not accept any parameters, so there is no way to tell the state of a promise from the finally method
  2. Finally will eventually return the value of the previous Promise object by default, but will return the peomise object of the exception if an exception is thrown
Promise.resolve('2').finally(() => {console.log('finally2') return 'I am the value returned by finally2'}).then(res => { Console. log(' then function after finally2 ', res)})Copy the code

The results of

'finally2' then function '2' after 'finally2'Copy the code
Promise.resolve('1').finally(() => {console.log('finally1') throw new Error(' I am an exception thrown in finally ')}).then(res => { Console. log('finally then function ', res)}). Catch (err => {console.log(' catch error ', err)})Copy the code

The execution result is as follows:

'Finally1' 'catch Error' Error: I am an exception thrown in finallyCopy the code

But if you return new Error(‘ I am an exception thrown in finally ‘), then function 1 after finally will be printed.

. All,. Race method

.all Receives a set of asynchronous tasks, executes the asynchronous tasks in parallel, and executes the callback only after all the asynchronous tasks are completed.

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))
Copy the code

The results of

1
2
3
[1, 2, 3]
Copy the code

With ALL, you can perform multiple asynchronous operations in parallel and process all returned data in a single callback.

The. Then () callback after.all() receives the results of all asynchronous operations. And the array order in this result is the same as the array order received by promise.all ()!!

There is a scene is very suitable for this, some game materials more applications, when opening the web page, pre-load the need to use a variety of resources such as pictures, Flash and various static files. After everything is loaded, we’ll initialize the page.

.race also executes a set of asynchronous tasks, and then the asynchronous tasks are executed in parallel, preserving only the step completed by the first execution. The other methods still execute, but the execution results are discarded

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result: ', res))
  .catch(err => console.log(err))
Copy the code

The results of

1
'result: ' 1
2
3
Copy the code

What is the use of this race? There are many scenarios, such as using Race to set a timeout for an asynchronous request and perform the corresponding operation after the timeout

All and.race pass arrays with asynchronous tasks that throw exceptions, and only the first one thrown will be caught. Is caught by the second argument to then or. Catch, and does not affect the execution of other asynchronous tasks in the array

Handwritten Promise

1. The promise is a class

2. Promise, when the class is executed, an executor is passed in, which will execute immediately

3.Promise has three states

4. The status can only be changed from Pending to Fulfiled or pending to Rejected. Once the status changes, it cannot be changed again

5. Judge the status of the then method internally. Call the successful callback function in the successful state, and call the failed callback function in the failed state

6. In order to implement asynchronous logic, the then method can be used to determine whether a promise is in a pending state. If a promise is in a pending state, its callback function can be stored and called when the state changes

7. To implement chained calls, the then method returns a Promise object and gets the return value x when the callback is executed. Implement a function that determines the return value type if x === Promise throws a type error, returning x. teng if x is a subtype of promise, or resolve if it is normal.

8. Error catch: Use trycatch to catch errors

9. The then argument is optional. If it is a function, it returns a normal value

Implement static methods resolve and reject

Const PENDING = 'PENDING '; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; Class MyPromise {constructor(executor){// Executor is an executor, Try {executor(this.resolve, reject) This. Reject)} catch (error) {this.reject(error)}} // Success value = null; // Reason = null; // store success callback function onledCallbacks = []; OnRejectedCallbacks = []; Resolve = (value) => { This. Status === PENDING) {// This. // This. Value = value; / / resolve it will take out all the success callback execution while (this) onFulfilledCallbacks) length) {/ / Array. The shift (), remove the first element Array, and then () call, the shift is not pure functions, take out, Array will lose the element until the array is empty this. OnFulfilledCallbacks. The shift () (value)}}} / / change after a failed state reject = (reason) = > {/ / only state is waiting for, If (this.status === PENDING) {this.status = REJECTED; // This. Reason = reason; / / resolve it will take out all failed callback execution while (this) onRejectedCallbacks) length) {this. OnRejectedCallbacks. Shift () (reason)}}} then(onFulfilled, onRejected) { const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; // Create a MyPromise for the chain-call, Const promise2 = new MyPromise((resolve, Reject) => {const string MicroTask = () => {// Create a microtask and wait for promise2 to complete. Initialize queueMicrotask(() => {try {// This is a big pity; const x = realOnFulfilled(this.value); ResolvePromise (promise2, x, resolve, reject); } catch (error) {reject(error)}})} const rejectedMicrotask = () => {// Create a microtask to wait for promise2 to complete. Const x = realOnRejected(this.reason); ResolvePromise (promise2, x, resolve, reject); This is a big pity. This. Status === this is a big pity (this.status === PENDING) {rejectedMicrotask()} else if (this.status === PENDING) {this.status === PENDING; So the success callback callbacks and failure stored / / wait for implementing successful failure function transfer this. When onFulfilledCallbacks. Push (fulfilledMicrotask); this.onRejectedCallbacks.push(rejectedMicrotask); } }) return promise2; Static resolve (parameter) {// if (parameter instanceof MyPromise) {// if (parameter instanceof MyPromise) {// if (parameter instanceof MyPromise) {return parameter; Return new MyPromise(resolve => {resolve(parameter); }); } static reject(reason) {return new MyPromise((resolve, reject) => {reject(reason); }); }} function resolvePromise(promise2, x, resolve, reject) { Throws a TypeError and returns if (promise2 === x) {return reject(new TypeError('Chaining cycle detected for promise #< promise >'))} // If (x instanceof MyPromise) {// Execute x, call then, This will be a pity or pity // x. teng (value => resolve(value), reason => reject(reason)) // Simplify x.teng (resolve, Reject)} else{// resolve(x)}} module.exports = MyPromise;Copy the code

conclusion

  • Once a Promise status changes, it cannot be changed. (see 3.1)

  • .then and.catch both return a new Promise. (As evidenced by 👆1.4 above)

  • A catch catches errors that have not been caught by the upper layer, regardless of where it is connected. (see 3.2)

  • In a Promise, any return value that is not a Promise is wrapped as a Promise object, for example return 2 is wrapped as return promise.resolve (2).

  • A Promise’s.then or.catch can be called multiple times, but if the internal state of the Promise changes and a value is given, that value will be returned each time a.then or.catch is called. (see 3.5)

  • .then or. Catch returns an error object that does not throw an error, so it will not be caught by subsequent.catches. (see 3.6)

  • The value returned by. Then or. Catch cannot be the promise itself, otherwise an infinite loop will occur. (see 3.7)

  • The parameters of.then or.catch are expected to be functions, and pass-through occurs when passing non-functions. (see 3.8)

  • The. Then method takes two arguments, the first for the successful function and the second for the failed function, and sometimes you can think of catch as a shorthand for the second argument. (see 3.9)

  • The.finally method also returns a Promise, and when the Promise ends, the callback will be executed, whether resolved or Rejected.

  • Promise.all() takes a set of asynchronous tasks, executes them in parallel, and does not execute the callback until all the asynchronous operations are complete.

  • .race() also receives a set of asynchronous tasks and executes them in parallel, retaining only the results of the first completed asynchronous operation. The other methods still execute, but the results are discarded.

  • The array order in the promise.all () result is the same as the array order received by promise.all ().

  • If the array passed by all and race contains an asynchronous task that throws an exception, only the first error thrown will be caught, either by the second argument to then or by a subsequent catch. This does not affect the execution of other asynchronous tasks in the array.