Promise是一种异步编程的解决方案。
概述
Promise是一个代理,在创建时不会立即返回最终值,而是返回一个Promise实例对象,以便在未来某个时间点获取该值。对象的状态不受外界的影响,只有内部异步操作的结果可以决定当前处于何种状态,任何其他的操作都无法改变这状态。
Promise有三种状态:
- Pending:初始状态。
- Fulfilled/Resolved:意味着操作成功完成。
- Rejected:意味着操作失败。
创建的时候,Promise对象处于待定(Pending)状态。一旦状态转化为已兑现(Fulfilled/Resolved)状态或者已拒绝(Rejected)状态,就不能被更改,即最终状态会被锁定。此后再对该Promise对象添加回调函数,也会立即得到这个结果。这与事件机制(事件发生之后再去监听就得不到结果)是完全不同的。
状态转化示意图如下:
用法
创建一个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原型添加then和catch方法:
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递归调用自身,否则直接返回原始值。
具体的实现代码,可参考:传送门