小议Promise

132 阅读5分钟

一、Promise 概况

(一)设计初衷

在 JavaScript 中,Promise 主要用于解决异步编程中回调地狱带来的一系列问题,同时让异步代码的逻辑更加清晰、可维护和易于扩展。

解决回调地狱问题

回调地狱是指在处理多个异步操作时,由于嵌套使用回调函数,导致代码结构变得复杂、难以理解和维护。而 Promise 通过链式调用的方式,避免了多层嵌套回调函数的问题,让代码结构更加清晰。

const fs = require('fs');

fs.readFile('file1.txt', 'utf8', function (err, data1) {
    if (err) {
        console.error(err);
    } else {
        fs.readFile('file2.txt', 'utf8', function (err, data2) {
            if (err) {
                console.error(err);
            } else {
                fs.readFile('file3.txt', 'utf8', function (err, data3) {
                    if (err) {
                        console.error(err);
                    } else {
                        console.log(data1, data2, data3);
                    }
                });
            }
        });
    }
});

使用 Promise 可以将上述代码改写成链式调用的形式,使代码更易读:

const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);

readFile('file1.txt', 'utf8')
  .then(data1 => {
    return readFile('file2.txt', 'utf8');
  })
  .then(data2 => {
    return readFile('file3.txt', 'utf8');
  })
  .then(data3 => {
    console.log(data1, data2, data3);
  })
  .catch(err => {
    console.error(err);
  });

统一错误处理

在回调地狱中,每个回调函数都需要单独处理错误,导致错误处理代码分散,增加了代码的复杂度。而 Promise 提供了统一的错误处理机制,通过 .catch() 方法依赖错误的传递,可以捕获整个 Promise 链中的错误,使错误处理更加集中和方便。

更好的代码复用

Promise 可以将异步操作封装成独立的函数,返回一个 Promise 对象,这样就可以在不同的地方复用这些异步操作。例如,将读取文件的操作封装成一个返回 Promise 的函数:

const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);

function readFilePromise(filePath) {
  return readFile(filePath, 'utf8');
}

readFilePromise('file1.txt')
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.error(err);
  });

更清晰的异步操作状态管理

Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),并且状态一旦改变就不会再变。这种状态管理机制让开发者可以更清晰地了解异步操作的执行情况,便于进行后续的处理。例如,在异步操作进行中可以显示加载状态,操作成功后显示结果,操作失败后显示错误信息。

(二)设计结构

yuque_diagram.jpg

二、Promise 细节

(一)Promise 对象

Promise 对象核心构成:

  1. status:记录 Promise 状态,值可以为:pendingfulfilledrejeced 三选一。
  2. value:记录当 Promise 状态转为 fulfilled 时,获取的值。由调用者 promise 使用 resovle 传进来
  3. reason:记录当 Promise 状态为 rejected 时,获取的值。由调用者 promise 使用 reject 传进来
  4. resovlereject:一对函数(类似开关遥控器,控制 Promise 的状态变化,并可以传递成功/失败值至回调函数),是控制 pendingpromise 状态是转向 fulfilled 还是 rejected。用法:resovle(value)reject(reason)
  5. then:注册回调函数 x,创建一个新的 promise 用于包装回调函数 x 的返回结果,从而实现链式调用。

回调函数 x必须是异步执行,即使当前 x 符合执行条件,也需要异步执行。类似于放到 setTimeout内,如 setTimeout(x,0),本次主循环不执行。

(二)Promise 链式调用

Promisethen 方法可以返回一个新的包装 Promise。注意,每调用一次 then 方法做了两件事:

  1. then 方法参数的成功和失败回调函数注册,待 Promise 状态改变时集中调用
  2. 新创建一个包装 Promise,实现链式调用。包装 Promise 的状态根据成功/失败回调函数的返回值 x 来确定。存在 5 种依赖情况:
    • 若函数返回值x不是对象、函数,则直接将x兑现包装promise
    • 若函数返回值x是一个promise对象,则根据xfulfilledrejected 状态确定包装promise的状态,
    • 若函数返回值x是一个对象或函数,但x没有属性then,则包装promise的状态为rejected
    • 若函数返回值x是一个对象或函数,x有属性then,但then不是函数,则用x兑现包装promise
    • 若函数返回值x是一个对象或函数,x有属性then,且then是函数,则调用then函数状态确定包装promise状态

以下代码为例

const promise = new Promise((resolve, reject) => {
  resolve('Promise1 成功');
}).then(function fn1(value){return new Promise((resolve,reject)=>{reject("Promise2 失败")})})
  .then(function fn2(value){
  console.log('成功:',value);
},function fn3(reason){
  console.log('失败:',reason);//控制台输出“失败: Promise2 失败 ”
})

我理解的链式调用思路如下:

image.png

(三)异步和同步

Promise 异步、同步程序块执行的原则:

  1. 同步调用传入 Promise 构造器的参数方法
  2. 异步调用 Promise使用then注册的回调函数
  3. 在链式调用中,下一个 then 注册的回调函数较上一个 then 注册的回调函数异步执行
console.log("00")
const promise = new Promise((resolve, reject) => {
  console.log("11");//与主程序同步调用
  resolve('Promise1 成功');
}).then(
  function fn1(value){
    console.log("33");//第一个then内的注册回调函数,相比Promise构造器内传入的方法为异步调用
    return new Promise((resolve,reject)=>{reject("Promise2 失败")})
  }).then(
    function fn2(value){
      console.log('成功:',value);
    },
    function fn3(reason){
      console.log("44");//第二个then内的注册回调函数,相比第一个then内注册回调函数为异步调用
      console.log('失败:',reason);
    }
  )
console.log("22");

以上代码按照 00、11、22、33、44 顺序输出。

三、结束语

本文主要聚焦我认为最需要了解的Promise链式调用工作逻辑,解释链式调用后一共产生了多少个Promise对象,各Promise对象之间的关系是怎么样的,Promise如何进行状态传递和值传递。了解这些对学习Promise的各种方法使用、async await知识都会有所帮助。希望本文对你在学习Promise知识中有所启发。