A Promise Done

1. What is Promise?

In JavaScript, Promise s are a way of handling asynchronous operations. Promise allows us to handle asynchronous operations more gracefully without nesting callback functions.

● Promise is a new technology (ES6 specification), a new solution for asynchronous programming in JS (the old solution is to simply use callback functions, which is easy to form callback hell)

● Common asynchronous operations: ①fs file operation ②database operation ③Ajax ④timer

● Concrete expression:

  1. From a grammatical point of view: Promise is a constructor (it has methods such as all, reject, and resolve, and its prototype has methods such as then, catch, etc.)
  2. From a functional point of view: the promise object is used to encapsulate an asynchronous operation and can obtain its success/failure result value

2. Create a Promise

Creating a Promise is very simple, just use the Promise constructor. The Promise constructor takes a function as an argument, which is called an executor function. The executor function has two parameters: resolve and reject. When the asynchronous operation succeeds, resolve is called, passing the result value as a parameter; when the asynchronous operation fails, reject is called, passing the error object as a parameter.

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else {
    reject(reason);
  }
});

3. Using Promise s

There are two ways to use Promise: then method and catch method.

1.then method

The then method is used to register a callback function when the Promise succeeds. When the Promise succeeds, the then method will be called, passing the result value of the Promise as a parameter.

promise.then((result) => {
  // Handles the result value when the Promise succeeds
});

2.catch method

The catch method is used to register a callback function when the Promise fails. When the Promise fails, the catch method is called, passing the Promise failed error object as a parameter.

promise.catch((error) => {
  // Handle the error object when the Promise fails
});

4. Promise.resolve and Promise.reject

1.Promise.resolve method

In JavaScript, Promise.resolve is a method that returns a Promise object that resolves with the given value. If the value is itself a Promise, return that Promise; otherwise, return a new Promise object that resolves with the value. It is typically used to convert a non-Promise value into a Promise object for use in a then method.

Promise.resolve(value):

value: The parameter that will be resolved by the Promise object, which can also be a successful or failed Promise object

Return a Promise object resolved with the given value, if the parameter itself is a Promise object, return the Promise object directly

  • 1. If the incoming parameter is an object of non-Promise type, the returned result is a successful promise object
let p1 = Promise.resolve(521);
console.log(p1); // Promise {<fulfilled>: 521}
  • 2. If the incoming parameter is a Promise object, the result of the parameter determines the result of resolve
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK'); // successful Promise
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})

2.Promise.reject method

Promise.reject(reason):

reason: the reason for the failure

Description: Returns a failed promise object

let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');

let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));

console.log(p);
console.log(p2);
console.log(p3);   //The promise returned by reject calls resolve

● The Promise.resolve()/Promise.reject() method is a syntactic sugar to quickly get the Promise object

5. Promise state and state transition

Promise s have three states:

pending (in progress), fulfilled (successful), and rejected (failed). The state of Promise can only be changed from pending to fulfilled or rejected, and once the state transition is completed, it cannot be changed again.

Below is a simple example showing Promise state and state transitions.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('result');
  }, 1000);
});

console.log(promise); // Promise { <pending> }

promise.then((result) => {
  console.log(result); // result
  console.log(promise); // Promise { <fulfilled>:'result' }
});

console.log(promise); // Promise { <pending> }

In the example above, the initial state of the Promise is pending because the asynchronous operation takes 1 second to complete. When the asynchronous operation completes and succeeds, the Promise state transitions to fulfilled. In the then method, we can access the result value of the Promise. Finally, we print the Promise object again, and we can see that its state has changed to fulfilled.

If the asynchronous operation fails, we can call the reject method to change the Promise state to rejected.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('error'));
  }, 1000);
});

promise.catch((error) => {
  console.log(error); // Error: error
    console.log(promise); // Promise { <rejected>: Error: error }
});

In the above example, the asynchronous operation fails and the Promise transitions to rejected. In the catch method, we have access to the Promise's error object.

6. Several key issues of Promise

💡 a. How to change the state of promise?

1.resolve(value): If it is currently pending, it will become resolved

2.reject(reason): If it is currently pending, it will become rejected

3. Throw an exception: If it is currently pending, it will become rejected

💡 b. A promise specifies multiple success/failure callback functions, will they all be called?

Called when the promise changes to the corresponding state

const p = new Promise((resolve, reject) => {
//resolve(1)
reject(2)
})
p.then(
value => {},
reason => {console.log('reason',reason)}
)
p.then(
value => {},
reason => {console.log('reason2',reason)}
)
// reason 2
// reason2 2

💡 c. What determines the result state of the new promise returned by promise.then()?

(1) Simple expression: determined by the execution result of the callback function specified by then()

(2) Detailed expression:

① If an exception is thrown, the new promise becomes rejected, reason is the exception thrown

② If any non-promise value is returned, the new promise becomes resolved, and value is the returned value

③ If another new promise is returned, the result of this promise will become the result of the new promise

💡 d. How does promise connect multiple operation tasks in series?

(1)promise of then() returns a new promise,can be combined then() The chain call of
(2)pass then The chained call concatenates multiple synchronized/asynchronous task

new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Execute task 1(asynchronous)')
resolve(1)
}, 1000)
}).then(
value => {
console.log('Results of Task 1', value)
console.log('Execute task 2(Synchronize)')
return 2 // The synchronous task directly return s the result
}
).then(
value => {
console.log('Results of Task 2', value)
return new Promise((resolve, reject) => { // Asynchronous tasks need to be wrapped in Promise objects
setTimeout(() => {
console.log('Execute task 3(asynchronous)')
resolve(3)
}, 1000)
})
}
).then(
value => {
console.log('Result of task 3', value)
}
)
// Execute task 1 (asynchronously)
// Task 1 results 1
// Execute task 2 (synchronous)
// Results of Task 2 2
// Execute task 3 (asynchronously)
// Task 3 results 3

💡 e, Promise abnormal penetration (transmission)?

(1) When using promise's then chain call, you can specify the failed callback at the end

(2) Any exception in the previous operation will be passed to the last failed callback for processing

new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
// onRejected1() 1

The result of the failure is processed layer by layer, and finally passed to the catch

7. Promise.all and Promise.race

In addition to the basic Promise, ES6 also provides two static methods: Promise.all and Promise.race. These two methods allow us to handle multiple Promise instances more conveniently.

1.Promise.all

The Promise.all method is used to wrap multiple Promise instances into a new Promise instance. The new Promise instance will only succeed if all Promise instances succeed; the new Promise instance will fail if any Promise instance fails.

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);

Promise.all([promise1, promise2]).then((results) => {
  console.log(results); // [1, 2]
});

In the example above, the Promise.all method takes an array of Promise instances as a parameter and returns a new Promise instance. When all Promise instances fulfill, a new Promise instance succeeds, passing an array of the resulting values ​​of all Promise instances as arguments.

2.Promise.race

The Promise.race method is used to wrap multiple Promise instances into a new Promise instance. The new Promise instance will either succeed or fail when the fastest Promise instance among them succeeds or fails.

const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(2);
  }, 2000);
});

Promise.race([promise1, promise2]).then((result) => {
  console.log(result); // 1
});

In the example above, the Promise.race method takes an array of Promise instances as a parameter and returns a new Promise instance. When the fastest Promise instance among them succeeds or fails, a new Promise instance succeeds or fails, passing the result value or error object of the first successful or failed Promise instance as an argument.

In layman's terms, it means that you may send me several asynchronous requests, and I will request them, but as long as one completes the request first, I will return its result (success/failure) to you. As for the rest of the requests, I only ask for you, I don’t care if he is right or wrong, I only pay attention to the fastest one.

8. Promise application scenarios

1. Avoid callback hell

Promise allows us to handle asynchronous operations more gracefully and avoid callback hell. When using Promise, we should try to avoid nesting callback functions, but use Promise chain calls.

asyncFunc1()
  .then((result1) => {
    return asyncFunc2(result1);
  })
  .then((result2) => {
    return asyncFunc3(result2);
  })
  .then((result3) => {
    console.log(result3);
  })
  .catch((error) => {
    console.log(error);
  });

2. Handling errors

When using Promise s, we should handle errors as much as possible. Promise errors can be handled using the catch method to avoid unhandled errors.

asyncFunc()
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

3. Return Promise

When writing a function, if there is an asynchronous operation inside the function, we should return a Promise instance so that the external code can use Promise to handle the asynchronous operation.

function asyncFunc() {
  return new Promise((resolve, reject) => {
    // asynchronous operation
    // On success call resolve(result)
    // Call reject(error) on failure
  });
}

9. Handwritten Promise Implementation Principle

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
    onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    });

    return promise2;
  }

  resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected'));
    }

    let called = false;

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      try {
        const then = x.then;

        if (typeof then === 'function') {
          then.call(x, (y) => {
            if (called) return;
            called = true;
            this.resolvePromise(promise2, y, resolve, reject);
          }, (r) => {
            if (called) return;
            called = true;
            reject(r);
          });
        } else {
          resolve(x);
        }
      } catch (error) {
        if (called) return;
        called = true;
        reject(error);
      }
    } else {
      resolve(x);
    }
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new MyPromise((_, reject) => {
      reject(reason);
    });
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;

      for (let i = 0; i < promises.length; i++) {
        promises[i].then((result) => {
          results[i] = result;
          count++;

          if (count === promises.length) {
            resolve(results);
          }
        }, reject);
      }
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(resolve, reject);
      }
    });
  }
}

In the above code, we created a MyPromise class which contains the basic implementation of Promise. We have implemented the basic functions of Promise, including state change, implementation of then method and catch method, chain call of Promise, error handling and implementation of static methods resolve, reject, all and race.

10. Summary

Promise is a way to handle asynchronous operations, which allows us to handle asynchronous operations more gracefully. There are some best practices we should follow when working with Promises, including avoiding callback hell, handling errors, and returning Promises. Hope this article can help you use Promise better.

Tags: Javascript Promise

Posted by hyzdufan on Wed, 08 Mar 2023 16:02:45 +0530