11.1 期约

期约是对尚不存在结果的一个替身。ECMAScript 6增加了对Promises/A+规范的完善支持,即Promise类型,成为了主导性的异步编程机制。

11.1.1期约基础

ECMAScript 6新增的引用类型Promise,可以通过new操作符来实例化。创建新期约时需要传入执行器(executor)函数作为参数。下面用一个空函数对象来应付一下解释器(若不提供执行器函数则会抛出SyntaxError):

let p = new Promise(() => {});
setTimeOut(console.log, 0, p);	//	Promise <pending>
1、期约状态机

期约是一个有状态的对象,拥有三种状态:pending(待定)、fulfilled(兑现) / resolved(解决)、rejected(拒绝)。

待定(pending):是期约的初始状态,既没有被兑现,也没有被拒绝。在待定状态下可以落定为另外两种状态。一旦落定后就不可改变,是不可逆的。

兑现(fulfilled):有时也直接称为“已解决(resolved)”。意味着操作成功完成。此时会有一个永久的私有内部值(value),不可修改。内部值可选,默认为undefined。

拒绝(rejected):意味着操作失败。此时会有一个永久的理由(reason)(通常是 Error 对象),该值是私有的内部值,不可修改。理由可选,默认为undefined。

2、通过执行函数控制期约状态

由于期约的状态是私有的,所以只能在内部进行操作。内部的操作在期约的执行器函数中完成。执行期函数主要有两个职责:初始化期约的异步行为和控制状态的转换。其中控制状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常命名为resolve()和reject()。调用resolve()会把状态切换为兑现,调用reject()会把状态切换成拒绝,另外reject()也会抛出错误。

const myPromise = new Promise((resolve, reject) => {
  // 这里是执行器(executor)函数,由 JavaScript 引擎提供,立即执行。

  // 模拟异步工作
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('Operation succeeded!'); // 将状态由 pending 变为 fulfilled
    } else {
      reject(new Error('Operation failed!')); // 将状态由 pending 变为 rejected
    }
  }, 1000);
});

无论resolve()和reject()哪个被调用了,状态转换都无法撤销了,继续修改状态会静默失败:

let p = new Promise((resolve, reject) => {
  resolve();
  reject();	//没有效果
})
setTimeout(console.log, 0, p);	// Promise <resolved>

为避免期约卡在待定状态,可以添加一个定时退出功能:

let p = new Promise((resolve, reject) => {
  setTimeout(reject, 10000);	//10秒后调用reject()
  //执行函数的逻辑
})
setTimeout(console.log, 0, p);	// Promise <pending>The promise rejected with the reason "undefined".
setTimeout(console.log, 11000, p);	// 11秒后再检查状态:Promise <rejected>,会抛出The promise rejected with the reason "undefined".
3、Promise.resolve()

可以通过调用Promise.resolve()静态方法,实例化一个解决的期约。下面两个期约实例实际上是一样的:

let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();

Promise.resolve() 会根据传入的 参数 类型有不同的处理方式,多余的参数会被省略:

情况一:传入普通值(非 Promise 对象)

返回一个已兑现的 Promise,其兑现值就是这个普通值。

// 传入字符串
const p1 = Promise.resolve('Hello');
p1.then(value => console.log(value)); // "Hello"

// 传入对象
const p2 = Promise.resolve({ name: 'John', age: 30 });
p2.then(value => console.log(value)); // {name: "John", age: 30}

// 传入数组
const p3 = Promise.resolve([1, 2, 3]);
p3.then(value => console.log(value)); // [1, 2, 3]

// 甚至传入 undefined
const p4 = Promise.resolve();
p4.then(value => console.log(value)); // undefined
情况二:传入 Promise 对象

直接返回这个 Promise 对象本身(幂等操作)。

const originalPromise = new Promise(resolve => {
  setTimeout(() => resolve('Original'), 1000);
});

const wrappedPromise = Promise.resolve(originalPromise);

console.log(originalPromise === wrappedPromise); // true

// 两个 Promise 实际上是同一个对象
wrappedPromise.then(value => console.log(value)); // 1秒后输出 "Original"
4、Promise.reject()

Promise.reject() 用于创建一个立即拒绝(rejected) 的 Promise 对象。Promise.reject() 总是返回一个立即拒绝的 Promise,拒绝原因就是你传入的 参数。

const rejectedPromise = Promise.reject(new Error('Something went wrong!'));

rejectedPromise.catch(error => {
  console.error(error.message); // "Something went wrong!"
});

但是不同于Promise.resolve()的幂等逻辑,当给它传入一个期约对象,这个期约会成为它返回的拒绝期约的理由:

setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
//	Promise	<rejected>: Promise <resolved>
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐