Promise对象

223 阅读6分钟

Promise是一种异步编程的解决方案。

概述

Promise是一个代理,在创建时不会立即返回最终值,而是返回一个Promise实例对象,以便在未来某个时间点获取该值。对象的状态不受外界的影响,只有内部异步操作的结果可以决定当前处于何种状态,任何其他的操作都无法改变这状态。

Promise有三种状态:

  • Pending:初始状态。
  • Fulfilled/Resolved:意味着操作成功完成。
  • Rejected:意味着操作失败。

创建的时候,Promise对象处于待定(Pending)状态。一旦状态转化为已兑现(Fulfilled/Resolved)状态或者已拒绝(Rejected)状态,就不能被更改,即最终状态会被锁定。此后再对该Promise对象添加回调函数,也会立即得到这个结果。这与事件机制(事件发生之后再去监听就得不到结果)是完全不同的。

状态转化示意图如下:

image.png

用法

创建一个Promise实例对象:

const p = new Promise((resolve, reject) => {
    const errMsg = '';
    
    // 异步操作成功
    if (!errMsg) {
        return resolve('ok');
    }

    return reject(errMsg)
});

Promise构造函数接受一个函数作为参数,该函数有两个参数,分别是resolve和reject。它们都是函数对象。

resolve函数会将Promise状态从“待定”转化为“已兑现”,并将结果作为参数传递出去。如上所示,其最终值为字符串ok。而reject函数则会将Promise状态从“待定”转化为“已拒绝”,并将错误作为参数传递出去。

需要注意的是,调用resolve或者reject并不会终止Promise构造函数的回调函数的执行:

new Promise((resolve) => {
    resolve(1);
    console.log('start');
})
    .then(console.log);

// 输出结果为:
// start
// 1

上述代码中,调用resolve函数之后,其后的console.log也会执行。

一般情况下,调用resolve或reject后就表示Promise就已经完成了,后续的操作应该放在 .then 方法里,而不应该写在resolve或reject的后面。所以,常见的写法是在函数前加上return语句,如:

new Promise((resolve) => {
    return resolve(1);
});

Promise.prototype.then

Promise实例的 .then 方法,用于对实例添加状态转变(Fulfilled/Resolve)时的回调函数。它的第一个参数是Promise兑现时的回调函数,第二个参数是Promise拒绝时的回调函数(可选)。.then方法返回的是一个新的Promise实例,可用于链式调用。

获取Promise结果

new Promise((resolve) => {
    return resolve(100);
    })
    .then(console.log);
        
// 输出结果为:
// 100

捕获Promise异常

new Promise((resolve, reject) => {
    return reject(new Error('abort!!!'));
})
    .then(console.log, (err) => console.error(err.message));
    
// 输出结果为:
// abort!!!

链式调用

new Promise((resolve) => {
    return resolve(1);
})
    .then((val) => val * 2)
    .then(console.log);
    
// 输出结果为:
// 2

Promise.prototype.catch

Promise实例的catch方法,是 .then(null, onRejected) 的语法糖,用于指定发生错误时的回调函数(Promise被拒绝或执行过程中产生同步异常)。它同样返回一个新的Promise实例。

捕获Promise失败原因

new Promise((resolve, reject) => {
    return reject(new Error('abort!!!'));
})
    .catch((err) => console.error(err.message));
    
// 输出结果为:
// abort!!!

捕获Promise执行中的同步异常

new Promise((resolve, reject) => {
    a + 100;
    return resolve(1);
})
    .catch((err) => console.error(err.message));
    
// 输出结果为:
// a is not defined

捕获Promise异常并返回新的结果

new Promise((resolve, reject) => {
    return reject(new Error('abort!!!'));
})
    .catch((err) => {
        console.error(err.message);
        
        return 10;
    })
    .then(console.log);
    
// 输出结果为:、
// abort!!!
// 10

需要注意的是,当then方法中指定Promise被拒绝时的回调函数时,catch方法指定的回调函数不被调用:

new Promise((resolve, reject) => {
    return reject(new Error('abort!!!'));
})
    .then(null, (err) => console.error(`then say: ${err.message}`))
    .catch((err) => console.error(`catch say: ${err.message}`))
    
// 输出结果为:
// then say: abort!!!

这是为什么呢? 因为上面的 .then 方法返回的是一个状态为已兑现(Fulfilled/Resolve)的新的Promise实例,后面紧跟的 .catch 方法捕获的是该新的Promise对象产生的异常。我们把上面的代码改造下,便于更好的理解:

new Promise((resolve, reject) => {
    return reject(new Error('abort!!!'));
})
    .then(null, (err) => {
        console.error(`then say: ${err.message}`);
        return 10;
    })
    .catch((err) => console.error(`catch say: ${err.message}`))
    .then(console.log)
    
// 输出结果为:
// then say: abort!!!
// 10

简单应用

Promise一个典型的应用,就是将异步回调的形式改写为返回一个Promise对象的异步函数,可以结合await操作符一起使用:

// 延时函数
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

await sleep(1000);

将普通对象转为Promise对象,便于链式调用:

Promise.resolved('Hello world!');

// 等价于(rejected同理)

new Promise((resolve) => resolve('Hello world!'));

* Promise实现

最后,为了更好的理解Promise的机制,让我们自己来尝试简单地实现Promise的功能。

第一步,我们需要定义Promise的三种状态:

const STATE = {
  FULFILLED: Symbol('fulfilled'),
  REJECTED: Symbol('rejected'),
  PENDING: Symbol('pending'),
};

使用Symbol值为了确保是一个唯一的值,不会和其他字符串值产生冲突。

第二步,定义Promise函数:

const STATE_KEY  = Symbol('state');
const FULFILLED_HANDLERS_KEY  = Symbol('fulfilledHandlers');
const REJECTED_HANDLERS_KEY = Symbol('rejectedHandlers');

const VALUE_KEY = Symbol('value');
const REASON_KEY = Symbol('reason');

function Promise(callback) {
  this[STATE_KEY] = STATE.PENDING;
  this[FULFILLED_HANDLERS_KEY] = [];
  this[REJECTED_HANDLERS_KEY] = [];

  if (typeof callback !== 'function') {
    throw new Error('"callback" must be a function object');
  }

  const resolve = (value) => {
    if (this[STATE_KEY] === STATE.PENDING) {
      this[STATE_KEY] = STATE.FULFILLED;
      this[VALUE_KEY] = value;
      this[FULFILLED_HANDLERS_KEY].forEach((fn) => fn());
    }
  }

  const reject = (reason) => {
    if (this[STATE_KEY] === STATE.PENDING) {
      this[STATE_KEY] = STATE.REJECTED;
      this[REASON_KEY] = reason;
      this[REJECTED_HANDLERS_KEY].forEach((fn) => fn());
    }
  }

  try {
    callback(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

注意:

  • 需要将异步操作的结果和错误原因保存在实例对象上,否则“延迟”添加的回调函可能会无法正确调用;
  • Symbol值作为属性名,避免外部通过Promise实例直接修改属性值。

第三步,为Promise原型添加thencatch方法:

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (typeof onFulfilled !== 'function') {
    onFulfilled = (value) => value;
  }

  if (typeof onRejected !== 'function') {
    onRejected = (reason) => { throw reason };
  }

  return new Promise((resolve, reject) => {
    if (this[STATE_KEY] === STATE.FULFILLED) {
      process.nextTick(() => {
        try {
          wrap(onFulfilled(this[VALUE_KEY]), resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    } else if (this[STATE_KEY] === STATE.REJECTED) {
      process.nextTick(() => {
        try {
          wrap(onRejected(this[REASON_KEY]), resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    } else {
      this[FULFILLED_HANDLERS_KEY].push(() => {
        process.nextTick(() => {
          try {
            wrap(onFulfilled(this[VALUE_KEY]), resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      });

      this[REJECTED_HANDLERS_KEY].push(() => {
        process.nextTick(() => {
          try {
            wrap(onRejected(this[REASON_KEY]), resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      });
    }
  });
}

Promise.prototype.catch = function (onRejected) {  
  if (typeof onRejected !== 'function') {
    onRejected = (reason) => { throw reason };
  }

  return new Promise((resolve, reject) => {
    if (this[STATE_KEY] === STATE.REJECTED) {
      process.nextTick(() => {
        try {
          wrap(onRejected(this[REASON_KEY]), resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    } else if (this[STATE_KEY] === STATE.PENDING) {
      this[REJECTED_HANDLERS_KEY].push(() => {
        process.nextTick(() => {
          try {
            wrap(onRejected(this[REASON_KEY]), resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      });
    }
  });
}

.then 方法和 .catch 方法中,我们都先判断Promise的状态是否为已兑现或已拒绝。如果是,则直接执行回调函数;否则就加入对应的待执行队列中。两个方法返回的都是新Promise的实例对象。

细心的读者可能会发现,在实际执行回调函数时,都会通过一个名为wrap的函数包裹。这正是实现链式调用的关键所在,具体如下:

function wrap(value, resolve, reject) {
  // thenable object
  if (value && typeof value.then === 'function') {
    try {
      value.then.call(value, (value2) => {
        wrap(value2, resolve, reject);
      }, (reason) => {
        reject(reason);
      });
    } catch(err) {
      reject(err);
    }
  } else {
    resolve(value);
  }
}

如果value为thenable对象,则warp递归调用自身,否则直接返回原始值。

具体的实现代码,可参考:传送门