promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
promise用法可以参考:promise用法
promise/A+ 规范: promise/A+
promise/A+中文翻译: promise/A+中文
简单的使用就不在探究了。可以自行查看文档。这里只把使用上表现结合起来探究其原理,并手写一个Promise
promise主体
根据promise/A+规范
- promise是一个类。
- 每次new一个Promise时都需要传递一个执行器。是立即执行的
- 执行器中有两个参数resolve、reject
- 默认promise中有是三个状态,pendding、fulfilled、rejected
pendding -> resolve 就代表成功fulfilled
pendding -> reject 就代表成功rejected - 如果一旦状态改变为成功或失败,就不能再变回失败或成功了
- 每个promise都有一个then方法
先根据如下规范可以写出promise的大体框架
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
// 保存成功后的信息 resolve(222) -> 222
this.value = undefined;
// 保存失败后的错误 reject(333) -> 333
this.reason = undefined;
// 保存promise状态
this.status = PENDING;
// 调用resolve()时的回调
let resolve = value => {
this.value = value;
this.status = FULFILLED;
}
// 调用reject()时的回调
let resolve = reason => {
this.reason = reason;
this.status = REJECTED;
}
// new Promise(executor) 立即执行执行器,传入回调
executor(resolve, reject);
}
// 外界调用的then函数
then(onFulfilled, onRejected) {
// 当状态为成功时,调用p.then((res) => {})传入的第一个回调
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
// 当状态为成功时,调用p.then(null, (res) => {})传入的第二个回调
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
1.执行器executor在new Promise时是立即执行的。
new Promise((resolve, reject) => {
console.log(1)
})
console.log(2)
// 1
// 2
new Promise((resolve, reject) => {
setTimeout(() => {console.log(1)})
})
console.log(2)
// 2
// 1
2. 规范中规定: 如果一旦状态改变为成功或失败,就不能再变回失败或成功了
所以代码需要改写,增加判断当前状态是不是PENDDING,如果是才能修改状态
// 调用resolve()时的回调
let resolve = value => {
if(this.status === PENDDING) {
this.value = value;
this.status = FULFILLED;
}
}
// 调用reject()时的回调
let resolve = reason => {
if(this.status === PENDDING) {
this.reason= reason;
this.status = REJECTED;
}
}
3. 在使用中不关使用reject()可以抛出错误,直接使用throw new Error也可以进入rejected逻辑
let p = new Promise((resolve)=>{throw new Error(666)})
p.then(null, err => {console.log(err)})
// vendor.7ab21e702e7733b6b702.js:1 Error: 666
所以需要在调用执行器时加入try... catch
// new Promise(executor) 立即执行执行器,传入回调
try {
executor(resolve, reject);
} catch(e) {
// 如果有throw的错误,直接执行reject回调
reject(e);
}
4. 如果执行器中有异步操作。则调用then时有可能还是pendding状态。
let p = new Promise(resolve => {
setTimeout(() => {
resolve(666)
})
// 或者ajax请求等异步操作
});
所以需要增加逻辑判断。并利用发布订阅。在构造函数中定义onResolvedCallbacks和onRejectedCallbacks队列,如果是pendding状态则将回调push入队列中。在异步执行完毕调用resolve()或者reject()时。去回调中执行。
constructor {
// 再定义两个队列
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
// 调用resolve()时的回调
let resolve = value => {
if(this.status === PENDDING) {
this.value = value;
this.status = FULFILLED;
// 一旦调用resolve() 就把之前保存的异步回调都执行一遍
this.onResolvedCallbacks.forEach(fn => fn());
}
}
// 调用reject()时的回调
let resolve = reason => {
if(this.status === PENDDING) {
this.reason= reason;
this.status = REJECTED;
// 一旦调用reject() 就把之前保存的异步回调都执行一遍
this.onRejectedCallbacks.forEach(fn => fn());
}
}
}
then(onFulfilled, onRejected) {
if (this.status === PENDDING) {
this.onResolvedCallbacks.push(() => {
// todo
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(() => {
// todo
onRejected(this.reason);
})
}
}
回调为什么要定义成队列数组呢?
因为一个promise实例有可能有多个then调用。
let p = new Promise(resolve => {resolve(43)});
p.then(res => {console.log(1)});
p.then(res => {console.log(2)});
p.then(res => {console.log(3)});
// 上面一个实例p,有三个then函数调用。所以会把他的onFulfilled回调都放入数组中
5. 如果then中传入的不是函数
如果then中传入的不是函数,不会抛出错误,而会继续略过他继续向下传递
let p = new Promise(resolve => resolve(43));
p.then(undefined, 443).then(res => {console.log(res)})
// 43
所以在then方法中需要添加容错处理。
then(onFulfilled, onRejected) {
// 如果成功回调不是函数则将value继续向下传递,失败则将reason继续向后抛
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
}
then函数中的回调会放入微任务中。在下次eventLoop中执行。
let p = new Promise(resolve => resolve());
// 如果传入不是函数继续向下抛。则会再多执行一次then。需要2次eventLoop
p.then(2323, false).then(res => {console.log(1)});
// 放入微任务 需要1次eventLoop
p.then(res => {console.log(2)});
// 直接执行
console.log(3)
// 3
// 2
// 1
6. 如果resolve或者reject一个promise
如果是resolve(new Promise())的话,就用这个promise的状态作为下个then的状态。
如果是reject(new Promise())的话,不需要执行直接返回。
new Promise(resolve => {
resolve(new Promise(resovle => resovle(1)))
}).then((res) => {
console.log(res); // 1
})
new Promise((resolve, reject) => {
reject(new Promise(resovle => resovle(1)))
}).then(null, (res) => {
console.log(res); // Promise {<resolved>: 1}
})
需要在成功的resolve()回调中增加判断
const resolve = value => {
// 如果resolve()参数传入的是个promise,直接执行它的then方法。
// 需要等待他执行完毕,拿到他的状态并且返回一个新的promise
if (value instanceOf Promise) {
return value.then(resolve, reject);
}
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(fn => fn());
}
}
实例:
new Promise(resolve => {
resolve(new Promise(resovle => resovle(1)))
}).then(() => {
console.log("tick 3");
}).then(() => {
console.log('tick 4')
});
new Promise(resovle => resovle(1)).then(() => {
console.log("tick 1");
}).then(() => {
console.log("tick 2");
});
// 1 2 3 4
// 因为resolve()一个promise需要等待他执行完毕,并且then方法返回一个新的promise。
// 相当于如下操作:
new Promise(resolve => resolve(1)).then(res => {
// 调用resolve中promise的then,返回一个新的promise。
// 这个promise的resolve值已经是普通值了。如果继续嵌套那就继续递归。
// 如果then中return promise 还是会等待他执行完then方法的。
return new Promise(resolve => resolve(res));
}).then((res) => {
console.log("tick 3",res);
}).then((res) => {console.log('tick 4', 1)});
// 也就相当于多了两次then。
new Promise(resolve => resolve(1)).then(res => res).then(res => res).then(res => {
console.log('tick 3')
}).then(() => {
console.log('tick 4')
});
基本架构
所以根据上述promise/A+规范。写出最基本的Promise源码如下。
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor (executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (value instanceOf Promise) {
return value.then(resolve, reject);
}
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(fn => fn());
}
}
const reject = reason => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}
网友评论