Promise
Published on Feb 25, 2023, with 9 view(s) and 0 comment(s)
Ai 摘要:本文介绍了JavaScript中Promise的概念及其使用方法。首先通过回调函数处理异步请求的痛点(如回调地狱)引出Promise的优势,详细解析了Promise的三种状态(pending/fulfilled/rejected)和基本用法。重点说明了then/catch/finally等实例方法的特点和链式调用规则,以及Promise.resolve/all/allSettled/race/any等类方法的应用场景。通过代码示例对比展示了Promise如何更优雅地处理异步操作,解决传统回调模式的嵌套问题,提升代码可读性和可维护性。

一,模拟异步请求

使用回调函数返回请求到的异步数据

function requestData(url, successCallback, failtureCallback) {
  // 异步操作:网络请求
  setTimeout(() => {
    if (url === "sun") {
      // 请求成功
      const res = ["sun", "james", "lebron"];
      successCallback(res);
    } else {
      // 请求失败
      const error = "路径错误";
      failtureCallback(error);
    }
  }, 3000);
}

requestData(
  "sn",
  function (res) {
    console.log(res);
  },
  function (error) {
    console.log(error);
  }
);

回调函数的痛点:

  • 当需要多个异步操作嵌套使用的话会导致代码的可读性差,代码不规整,造成回调地狱。

  • 异步请求的定义与使用需要大量的沟通成本。

使用Promise重构什么是Promise? promise是一个类。在网络请求函数中可以给调用者返回一个承诺对象,通过这个承诺可以拿到成功或者失败的返回响应。 在这个承诺对象中需要传入一个executor回调函数,这个回调函数会立即执行,并且需要给这个函数传入另外两个回调函数:resolver和reject。当调用resolve函数时,会执行promise对象的then方法,当调用reject函数时,会执行promise对象中的catch方法。

function requestData(url) {
  // 异步操作:网络请求
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "sun") {
        // 请求成功
        const res = ["sun", "james", "lebron"];
        resolve(res);
      } else {
        // 请求失败
        const error = "路径错误";
        reject(error);
      }
    }, 3000);
  });
}

requestData("sun")
  .then((res) => {
    console.log(res); //[ "sun", "james", "lebron" ]
  })
  .catch((error) => {
    console.log(error); //路径错误
  });

Promise的代码结构Promise使用过程,可以将它划分成三个状态:

  • 1,待定(pending): 初始状态,既没有被兑现,即也没有被拒绝;当执行executor中的代码时,处于该状态;

  • 2,已兑现(fulfilled): 意味着操作成功完成,即执行了resolve时,处于该状态;

  • 3,已拒绝(rejected): 意味着操作失败,即执行了reject时,处于该状态;

new Promise((resolve, reject) => {
  resolve("成功"), 
  reject("拒绝");
}).then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);

resolve方法参数

  • 1,如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数。

  • 2,如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态。

  • 3,如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态。

const newPromise = new Promise((resolve, reject) => {
  resolve("传入的是一个promise对象");
});
const obj = {
  then: function (resolve, reject) {
    resolve("传入的是一个具有then方法的对象");
  },
};
new Promise((resolve, reject) => {
  // resolve("成功"),
  // resolve(newPromise);
  resolve(obj);
  reject("拒绝");
}).then(
  (res) => {
    // console.log(res); //"成功"
    // console.log(res); //"传入的是一个promise对象"
    console.log(res); //"传入的是一个具有then方法的对象"
  },
  (err) => {
    console.log(err);
  }
);

then方法then方法接受两个参数:

  • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;

  • reject的回调函数:当状态变成reject时会回调的函数;

then方法的多次调用:

const promise = new Promise((resolve, reject) => {
  resolve("aaa");
});
promise.then((res) => {
  console.log("res1", res);
});
promise.then((res) => {
  console.log("res2", res);
});
// res1 aaa
// res2 aaa

then方法的返回值: then方法的返回值也是一个Promise对象,所以可以对promise进行链式调用。then方法返回的Promise也有三种状态:

  • 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;

  • 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;

  • - 情况一:返回一个普通的值;

  • - 情况二:返回一个Promise;

  • - 情况三:返回一个thenable值;

  • 当then方法抛出一个异常时,那么它处于reject状态;

const promise = new Promise((resolve, reject) => {
  resolve("aaa");
});

promise
  .then((res) => {
    return "sun";
    // return "sun" 相当于return一个新的promise,并把执行resolve("sun")
    // return new Promise((resolve, reject) => {
    //   resolve("sun");
    // });
  })
  .then((res) => {
    console.log(res);
  });

catch方法一个Promise的catch方法是可以被多次调用的:每次调用我们都可以传入对应的reject回调;当Promise的状态变成reject或者executor中发生异常的时候,这些回调函数都会被执行; 在执行promise的then方法时传入的第二个回调函数会在executor中reject函数执行的时候触发。但是当链式调用的时候then方法需要传入两个回调函数会造成阅读性较差。所以就可以通过catch方法进行捕获。

const promise = new Promise((resolve, reject) => {
  resolve("sun");
  // reject("aaa");
  // throw new Error("有异常");
});

promise
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log("err1", err);
  });

需要注意的是,catch可以看作是一个es6语法糖,它是先捕获promise中的异常再捕获then中返回的new Promise对象的异常。

catch方法的返回值catch方法的返回值也是一个新的promise对象,然后把返回的内容resolve出去,注意不是reject出去。

finally方法finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。

const promise = new Promise((resolve, reject) => {
  resolve("sun");
  // reject("aaa");
  // throw new Error("有异常");
});

promise
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log("err1", err);
  })
  .finally(() => {
    console.log("finally方法执行");
  });

Promise类方法:resolve前面的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。 以下是Promise的一些类方法。 场景:有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。Promise.resolve的用法相当于new Promise,并且执行resolve操作。

const obj = {
  name: "sun",
};
function foo(obj) {
  return new Promise((resolve, reject) => {
    resolve(obj);
  });
}
foo(obj).then((res) => {
  console.log(res); //{ name: "sun" }
});

// 使用Promise.resolve
Promise.resolve(obj).then((res) => {
  console.log(res); //{ name: "sun" }
});

Promise类方法:all它的作用是将多个Promise包裹在一起形成一个新的Promise; 新的Promise状态由包裹的所有Promise共同决定:

  • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;

  • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1");
  }, 1000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2");
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3");
  }, 3000);
});

Promise.all([p1, p2, p3, "aaa"]).then((res) => {
  console.log(res); //[ "p1","p2", "p3", "aaa" ]
});

Promise类方法:allSettledall方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,是获取不到对应的结果的;

  • 在ES11(ES2020)中,添加了新的API Promise.allSettled。

  • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;

  • 并且这个Promise的结果一定是fulfilled的

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1");
  }, 1000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2");
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3");
  }, 3000);
});

Promise.all([p1, p2, p3, "aaa"])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err); //p3
  });

Promise.allSettled([p1, p2, p3, "aaa"]).then((res) => {
  console.log(res);
});
// [
//   { status: "fulfilled", value: "p1" },
//   { status: "fulfilled", value: "p2" },
//   { status: "rejected", reason: "p3" },
//   { status: "fulfilled", value: "aaa" }
// ]

Promise类方法:race如果有一个Promise有了结果,就希望决定最终新Promise的状态,那么可以使用race方法:race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;

Promise.race([p1, p2, p3]).then((res) => {
  console.log(res); //p1
});

Promise类方法:anyany方法是ES12中新增的方法,和race方法是类似的,any方法会等到一个fulfilled状态,才会决定新Promise的状态;如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态,会报一个AggregateError的错误。

Promise.any([p1, p2, p3]).then((res) => {
  console.log(res); //p1
});