一.什么是Promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
二.Promise的特性
-
promise对象代表一个异步操作,有三种状态:pending、fulfilled、reject.
-
任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变它的状态.
三.Promise的出现解决了什么问题
promise是其实就是一个异步编程的方案,在promise出现之前经常能出现所谓的"回调地狱"的嵌套代码:
step(1, function() {
step(2, function() {
step(3, function() {
……….
})
})
})
复制代码
都知道这种"回调地狱",是一种很让人恼火的代码书写格式,嵌套多层之后会让人阅读起来怀疑人生的。但是这种"回调地狱"真的就只有格式的问题嘛?显然不是。
一个来自《YDKJS》的例子:一个程序员开发了一个付款的系统,它良好的运行了很长时间。突然有一天,一个客户在付款的时候信用卡被连续刷了五次。这名程序员在调查了以后发现,一个第三方的工具库因为某些原因把付款回调执行了五次。在与第三方团队沟通之后问题得到了解决。
上面的例子就是一个 信任问题 回调函数的方式会引发很多信任问题,例如重复调用,调用太晚等等问题。那么promise怎么解决上述两个问题的呢?
1.可读性
首先是promise的then方法,支持我们把"回调"写在then方法中,让人阅读起来就像看同步代码一样享受.下面是用promise改写之后的代码:
Promise.resolve(1)
.then(step => ++step)
.then(step => ++step)
.then(step => ++step)
…
复制代码
2.信任问题
这种机制有点像事件的触发。它与普通的回调的方式的区别在于,普通的方式,回调成功之后的操作直接写在了回调函数里面,而这些操作的调用由第三方控制。在Promise的方式中,回调只负责成功之后的通知,而回调成功之后的操作放在了then的回调里面,由Promise精确控制。
Promise的状态不可逆改变,一旦发生改变就没有办法再改变成任何一种状态。Promise的特征保证了Promise可以解决信任问题。
对于回调过早的问题,由于Promise只能是异步的,所以不会出现异步的同步调用。即便是在异步操作之前的错误,也是异步的,并不是会产生同步(调用过早)的错误。
四.实现promise要注意的点
1.符合promise/A+规范
在promise/A+规范里面规范了promise中的状态类型,then方法的标准(约束条件)等,现在我们所用的所有promise库虽然有很多的扩展功能,但是都是基于promise/A+规范来实现的基本功能。
2.Promise的状态只能修改一次
promise的一个特性就是promise的状态只能从pending变成fulfilled,或者从pending变成rejected。并且这个状态是不可逆的,一旦状态发生改变,无论进行什么操作,这个promise实例的状态都不可更改,这也保证了使用promise的安全性。
3.then和catch需要异步调用
const p = new Promise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
console.log(value);
});
console.log('hello world');
// output: hello world 1
复制代码
可以看到上述代码先执行的是hello world然后打印的才是1。
这就是小标题所说then和catch是需要等到同步代码执行完且promise发生改变之后才能执行的,也就是异步执行的。
4.then可以链式调用且返回一个新的promise
const p = new Promise((resolve, reject) => {
resolve(1);
});
p.then((value) => {
return value + 1;
})
.then(value => {
console.log(value);
});
// output: 2
复制代码
上述代码是一个经典的promise的then方法的链式调用,每一个then方法都会返回一个新的promise,来调用一个新的then方法,从而实现链式调用。并且then方法返回的promise是一个新的promise,这个promise的值就是上一个then方法return出来的值。
5.then方法可以实现穿透效果
const p = new Promise((resolve, reject) => {
resolve(1)
});
p.then()
.then((value) => {
console.log(value);
});
// output: 1
复制代码
我们可以看到 promise实例p调用的第一个then方法并没有注册任何的回调函数,这个时候第二个then方法依然能够拿到value=1.这就是then方法的穿透效果,当我们给then方法传递的参数不是一个有效的函数的时候,promise会自动实现一个穿透的效果,确保下一个then方法可以正常调用。
五、写一个promise
const PENDING = 0; // 准备状态
const FULFILLED = 1; // 成功状态
const REJECTED = 2; // 失败状态
class Promise {
constructor(fn) {
this._state = PENDING;
this._data = undefined;
// 用来储存成功回调函数的数组
this._onFulfilledCallback = [];
// 用来储存失败回调函数的数组
this._onRejectedCallback = [];
// 当new一个promise实例的时候,立刻执行声明promise时候传递的回调函数
run(this, fn);
}
then(onFulfilled, onRejected) {
// 解决then方法的穿透问题,当传递的回调函数不是一个有效的函数的时候,
// 我们需要手动把成功函数的回调和失败函数的回调重新赋值。
if (typeof onFulfilled !== 'function') {
onFulfilled = data => data;
}
if (typeof onRejected !== 'function') {
onRejected = reason => { throw reason; }
}
// 定义一个新的promise供then方法返回
let promise2;
//当定义then方法的时候,promise是准备状态
if (this._state === PENDING) {
promise2 = new Promise((resolve, reject) => {
// 当promise是准备状态的时候,把then方法的成功回调push进成功回调数组里面
this._onFulfilledCallback.push((data) => {
try {
const x = onFulfilled(data);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
// 当promise是准备状态的时候,把then方法的失败回调push进失败回调数组里面
this._onRejectedCallback.push((reason) => {
try {
const x = onRejected(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
//当定义then方法的时候,promise是失败状态
if (this._state === REJECTED) {
promise2 = new Promise((resolve, reject) => {
// 根据promise/A+规范,then方法中的onFulfilled和onRejected方法需要
// 在只包含平台代码中的执行栈中执行(可以简单理解为这两个方法是异步执行的)
// 这里我们用setTimeout来模拟实现。
setTimeout(() => {
try {
// 执行成功回调
const x = onRejected(this._data);
// 处理回调函数的返回值
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 如果代码报错,直接reject掉这个promise,不让报错信息外泄
reject(e);
}
});
});
}
// 当定义then方法的时候,promise是成功状态
// 详细解释和成功状态同理
if (this._state === FULFILLED) {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
const x = onFulfilled(this._data);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
// 返回新的promise
return promise2;
}
catch(onRejected) {
// catch方法其实就是一个变形的then方法
// 所以我们直接调用当前promise的then方法,将成功回调置空,失败回调
// 直接使用catch方法传递进来的回调
return this.then(undefined, onRejected);
}
}
Promise.reject = (reason) => {
// 返回一个成功状态的promise
return new Promise((resolve, reject) => {
reject(reason);
});
}
Promise.resolve = (data) => {
// 返回一个失败状态的promise
return new Promise((resolve, reject) => {
resolve(data);
});
}
// 兼容deferred方法,跑promiseA+规范case的时候需要这个方法
Promise.deferred = () => {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
// 处理onFulfilled和onRejected方法的返回值
const resolvePromise = (promise2, x, resolve, reject) => {
if (promise2 === x) { // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错
return reject(new TypeError('循环引用'));
}
let called = false; // 避免多次调用
// 如果x是一个promise对象 (该判断和下面 判断是不是thenable对象重复 所以可有可无)
if (x instanceof Promise) { // 获得它的终值 继续resolve
if (x._state === PENDING) { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值
x.then(y => {
resolvePromise(promise2, y, resolve, reject);
}, reason => {
reject(reason);
});
} else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise
x.then(resolve, reject);
}
// 如果 x 为对象或者函数
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try { // 是否是thenable对象(具有then方法的对象/函数)
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, reason => {
if (called) return;
called = true;
reject(reason);
})
} else { // 说明是一个普通对象/函数
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
const resolve = (promise, data) => {
// 当resolve的是一个promise的时候
if (data instanceof Promise) {
// 直接调用这个promise的then方法,然后用这个promise的then方法,
// 来resolve或reject我当前promise的状态和值
return data.then(
d => resolve(promise, d),
r => reject(promise, r)
)
}
// promise状态的改变是异步的,这里给其用setTimeout进行包裹
setTimeout(() => {
// 当promise的状态已经不是准备状态时,不进行任何操作,确保状态不可变
if (promise._state !== PENDING) {
return;
}
promise._state = FULFILLED;
promise._data = data;
// reolve之后遍历成功回调数组中的方法,逐一执行
for (let callback of promise._onFulfilledCallback) {
callback(data);
}
});
}
// 详细解释同resolve
const reject = (promise, reason) => {
setTimeout(() => {
if (promise._state !== PENDING) {
return;
}
promise._state = REJECTED;
promise._data = reason;
for (let callback of promise._onRejectedCallback) {
callback(reason);
}
});
}
// 执行声明promise实例时注册的回调函数
const run = (promise, fn) => {
try {
// 给方法传递两个参数,这两个函数也就是我们在外部使用的reolve和reject
fn(
data => resolve(promise, data),
reason => reject(promise, reason)
);
} catch (e) {
reject(e)
};
}
// race方法,返回一个promise,这个promise的值是传递的promise数组中第一个发生状态改变的
// promise的值,这里需要注意,一旦有一个promise状态改变了,这个promise就会resolve
// 或者reject
Promise.race = (promises) => {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject);
});
});
}
// all方法会返回一个promie,当传递的promise数组中的promise全都是成功状态
// 的时候,返回的promise的值是一个所有传递的promise的值。当其中有一个promise是
// 失败状态的时候直接reject这个值
Promise.all = (promises) => {
return new Promise((resolve, reject) => {
const length = promises.length;
const done = gen(length, resolve);
promises.forEach((promise, index) => {
promise.then((value) => {
done(value, index);
}, reject)
});
});
}
const gen = (length, resolve) => {
let count = 0;
const result = [];
return function (value, index) {
result[index] = value;
if (++count === length) {
resolve(result);
}
}
}
module.exports = Promise;
复制代码
六、总结
当我们自己尝试模拟一次promise的时候,就会发现能更加深刻的理解promise,当需要分析代码执行顺序的时候,就能更多的从源码出发,而不是凭借感觉来判断输出。