1、promise的特点
- 参数接收一个函数,不传会报错
- new 一个 promise 会立即执行函数
- 默认pending状态,例:
console.log(new Promise(() => {}))); // Promise {<pending>} - 接收的函数有两个状态:resolved、rejected,一旦调用其中一个就不能被改变了
- 调用then方法:
p.then(fn1, fn2) // fn1 -- 正确的处理、fn2 -- 失败的处理 - then 返回 promise对象,可以链式调用
通过这几个特点,我们先写一个大体的框架:
function MyPromise(fn) {
// 默认接收函数
if (typeof fn !== 'function') {
throw Error(`Promise resolver ${fn} is not a function`);
}
const self = this; // 修改status时有一个this指向问题,需要保存一下
this.status = 'pending'; // promise默认状态
function resolved() {
self.status = 'resolved';
}
function rejected() {
self.status = 'rejected';
}
// 创建函数立即执行
// 这时发现没有resolved和rejected函数,说明两个函数是构造函数内部的,创建
fn(resolved, rejected);
}
上述代码实现了以下功能:
- 容错,new MyPromise时如果传的不是函数会报错,与Promise保持一致
- 正确传函数进去可以立即执行,同时如果没有调用resolved或者rejected,状态默认为pending
- 调用resolved和rejected方法后会改变成相应的状态(这里有个坑,status可以多次修改,下一步进行优化)
2、实现then
- then接收两个参数:resolved、rejected
- 两个参数都是方法同时是都能将方法中的实参返回
- then 返回 promise对象,可以链式调用
上代码:
2.1、前面代码的改造
function MyPromise(fn) {
if (typeof fn !== 'function') {
throw Error(`Promise resolver ${fn} is not a function`);
}
const self = this;
this.status = 'pending';
this.data = null; // 保存resolved或者rejected的实参
function resolved(data) {
// 填坑:pending只能是resolve或者rejected状态,且只能改一次
if (self.status === 'pending') {
self.status = 'resolved';
self.data = data;
}
}
function rejected(err) {
// 填坑:pending只能是resolve或者rejected状态,且只能改一次
if (self.status === 'pending') {
self.status = 'rejected';
self.data = err;
}
}
fn(resolved, rejected);
}
2.2、then方法的实现
MyPromise.prototype.then = function (onResolved, onRejected) {
const self = this; // 保存MyPromise的this
// 这里可以用this,因为在原型上写的函数,this指向MyPromise
if (this.status === 'resolved') {
// then 返回 promise对象
return new MyPromise(function(resolved, rejected) {
var res = onResolved(self.data);
// 为什么要判断?2.3进行详解
if (res instanceof MyPromise) {
res.then(resolved, rejected);
} else {
resolved(res);
}
});
}
if (this.status === 'rejected') {
return new MyPromise(function(resolved, rejected) {
var res = onRejected(self.data);
// 为什么要判断?2.3进行详解
if (res instanceof MyPromise) {
res.then(resolved, rejected);
} else {
resolved(res);
}
});
}
}
2.3、解释
为什么需要要做判断?
then链式调用返回有两种情况:
1、正常返回,这个时候不管是resolved还是rejected下一个then都是resolve接收
上述代码,p1创建的时候执行内部的函数,执行resolved并传入‘123’,执行第一个then的时候直接返回参数,执行第二个then时走的第一个函数,所以打印123res;
p2创建时执行的rejeceed,所以执行第一个then时走的是第二个函数,直接返回err,开始执行第二个then,你会发现这个then方法执行的是第一个函数,所以打印的也是123res
2、then返回一个新的promise的情况
上述代码,创建p3走的是resolved,在执行第一个then时,执行的是参数中的第一个函数。该函数返回一个新的promise且执行了rejected,会发现打印的结果是123err,说明第二个then执行的是其第二个形参
总结:
- 如果then返回的是promise,那么链式调用时,下一个then接收的是新promise的状态
- 如果不是,那么下一个then接收的就是resolved状态
3、异步处理
我们先看一下原生的promise怎么处理的,复制下面代码到控制台,查看结果:
new Promise((resolved, rejected) => {
setTimeout(() => resolved('123'), 3000);
}).then((data) => console.log(data));
我们会发现是等了3s之后再打印的123,再看下我们的:
new MyPromise((resolved, rejected) => {
setTimeout(() => resolved('123'), 3000);
}).then((data) => console.log(data));
只打印了undefined,why?
原因是new MyPromise时执行计时器,then并没有等待计时器执行完就先执行了,所以打印的是undefined。
**解决方法:**实现then方法时再判断一下self.status === 'pending',如果等于,那么将这些一次保存进一个数组中,等到执行resolved或者rejected方法的时候依次执行即可。
上代码:
function MyPromise(fn) {
...
// 定义两个数组保存异步代码中的函数
this.resolvedList = [];
this.rejectedList = [];
function resolved(data) {
if (self.status === 'pending') {
self.status = 'resolved';
self.data = data;
// 当状态改变的时候执行对应的数组里的函数
self.resolvedList.forEach((fn) => fn());
}
}
function rejected(err) {
if (self.status === 'pending') {
self.status = 'rejected';
self.data = err;
// 当状态改变的时候执行对应的数组里的函数
self.rejectedList.forEach((fn) => fn());
}
}
fn(resolved, rejected);
}
then方法
MyPromise.prototype.then = function (onResolved, onRejected) {
...
if (this.status === 'pending') {
return new MyPromise(function(resolved, rejected) {
// 需要通过立即执行函数保存onResolved
self.resolvedList.push((function(onResolved) {
// 即status === 'resolved' 那一段代码
return function() {
var res = onResolved(self.data);
if (res instanceof MyPromise) {
res.then(resolved, rejected);
} else {
resolved(res);
}
}
}(onResolved)))
// rejected也需要保存
self.rejectedList.push((function(onRejected) {
// 即status === 'rejected' 那一段代码
return function() {
var res = onRejected(self.data);
if (res instanceof MyPromise) {
res.then(resolved, rejected);
} else {
resolved(res);
}
}
}(onRejected)))
})
}
}
4、执行顺序
var p = new Promise((resolved, rejected) => {
console.log('1');
resolved('2');
})
p.then((res) => console.log(res));
console.log('3');
上述代码的执行顺序是:1、3、2,显然前面的代码没有实现。具体原因可以了解js的执行机制。
**解决**:这块的实现是将resolved和rejeceed中的执行代码放到setTimeout中
function MyPromise(fn) {
...
function resolved(data) {
// 将resolved中的执行代码放到setTimeout中
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'resolved';
self.data = data;
self.resolvedList.forEach((fn) => fn());
}
}, 0);
}
function rejected(err) {
// 将rejeceed中的执行代码放到setTimeout中
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'rejected';
self.data = err;
self.rejectedList.forEach((fn) => fn());
}
}, 0);
}
fn(resolved, rejected);
}
完整的代码详见:github.com/White-lamb/…