自我实现的Promise是不会变成微任务的,只能通过定时器去模拟。微任务是浏览器自己实现的。
Promise的特点
- 对象的状态不受外界影响;
- 一旦状态改变就不会在变,任何时候都可以得到这个结果;
- Promise将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
- 缺点:
- 无法取消promise,一旦创建它就会立即执行,无法中途取消;
- 如果不设置回调函数,Promise内部抛出的错误不会反应到外部;
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
简易版Promise
- Promise是一个类;
- Promise有三个状态 Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败);
- Promise的成功还是失败,用户自定义;
- Promise默认执行器(executor)立即执行;
- 每个Promise都有then方法,接手两个参数,一个是成功的回调,一个是失败的回调;
- 执行函数时发生异常也会执行失败的逻辑(try..catch...)
- 一旦成功就不能失败,反之亦然(只有等待态才能更改状态)。
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态
class Promise {
constructor(executor) {
this.status = PENDING; // 默认状态
this.value = undefined; // 成功返回值
this.reason = undefined; // 失败返回值
const resolve = value => {
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = value;
}
};
const reject = reason => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === RESOLVED) {
onFulfilled(this.value);
}
if (this.status === onRejected) {
onRejected(this.reason);
}
}
}
const promise = new Promise((resolve, reject) => {
console.log('executor');
resolve('resolved');
// throw new Error('失败');
// reject('rejected');
});
promise.then(
res => {
console.log(res);
},
err => {
console.log(err);
}
);
优化then方法
如果我在生成Promise实例的时候,放了一段异步代码,异步代码执行完后才resolve,那么此时调用then方法的时候,promise的状态时pending,成功和失败也不会执行
const promise = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('resolve')
}, 1000)
});
promise.then() //此时调用then方法,status的状态是pending,要在1秒以后才能拿到resolve状态
为解决上面的问题,我们需要在status为pending的时候,把传入的回调函数存入队列里面。当调用then方法的时候,在resolve或者reject函数中依次执行队列的函数。相当于发布订阅模式,先订阅,再发布。
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态
class MyPromise {
constructor(executor) {
this.status = PENDING; // 默认状态
this.value = undefined; // 成功返回值
this.reason = undefined; // 失败返回值
this.onResolvedCallbacks = []; // 存放成功回调
this.onRejectedCallbacks = []; // 存放失败回调
const resolve = value => {
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === RESOLVED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
// todo...
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
// todo...
onRejected(this.reason);
});
}
}
}
const promise = new MyPromise((resolve, reject) => {
console.log('executor');
setTimeout(() => {
resolve('resolved');
// reject('rejected');
}, 1000);
});
promise.then(
res => {
console.log('success1', res);
},
err => {
console.log('failed1', err);
}
);
promise.then(
res => {
console.log('success2', res);
},
err => {
console.log('failed1', err);
}
);
Promise中的链式调用
- Promise成功和失败的回调的返回值,可以传递到下一层的then;
- 如果返回的是普通值(非Promise和出错的情况),无论是失败的普通值,还是成功的普通值,都会传递到下一层的then的成功的回调中;
- 如果返回的是个Promise对象,会根据promise的状态决定传递到下一层then的成功或者失败回调;
- 如果then的回调函数中不写返回值,会默认返回undefined,undefined是普通值,会被传递到下一个then的成功的回调中;
- 如果离自己最近的then中没有错误处理,会向下查找,所以我们不需要写很多错误的回调。写最后一个then的错误的回调就好,如果所有then不传递错误的回调,那么会报错;
- 每次执行Promise.then的时候都会返回一个全新的promise,所以可以无限then下去,这点有区别于jQuery的链式调用。如果Promise返回的是一个this,因为之前的promise的状态是不能变更的,如果前一个then是成功,后一个then是失败,这样是不符合规范的,所以必须返回一个全新的Promise。
ajax('xxx.url')
.then(
res=> {
return 200;
// Promise.resolve('成功');
},
err=> {
return 404; // 这种情况会传递到下一层then的成功的回调
// throw new Error('错误') // 这种情况会传递到下一层then的失败的回调
}
)
.then(
res=> {
console.log(res);
},
err=> {
console.log(err);
}
)
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
// 1.循环引用 自己等待自己完成
// 可能会有promise2.then(()=> {return promise2}),这种自己等自己的情况
// 抛出一个类型错误,结束掉Promise
reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// resolvePromise要兼容所有的库,因为还有别人写好的Promise,为了保证和别人的代码和别的库一起使用
// 下面要做更严谨的判断
// called变量用来标识promise的状态(别的库可能会变更我们的promise状态)
// 如果promise是成功态,将不允许再走失败态,反之亦然。
// 如果返回的是个普通值,不用添加标识
let called;
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
// 有可能是一个promise
try {
const then = x.then;
// console.log('*****', x, then);
// Promise {status: '', value:'', ...} [Function: then]
if (typeof then === 'function') {
// 如果x.then是一个函数,就认为x是一个promise,promise才有then方法
// 执行then方法
then.call(
// 根据promise的状态决定是成功还是失败
x,
y => {
if (called) return;
called = true;
// 成功
// resolve(y);
// 因为y还可能是个promise,所以我们要递归调用,直到y变为普通值
resolvePromise(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
// 失败
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
// 防止失败走成功
if (called) return;
called = true;
reject(e);
}
} else {
// 普通值直接resolve
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = PENDING; // 默认状态
this.value = undefined; // 成功返回值
this.reason = undefined; // 失败返回值
this.onResolvedCallbacks = []; // 存放成功回调
this.onRejectedCallbacks = []; // 存放失败回调
const resolve = value => {
if (value instanceof Promise) {
// 如果value是个Promise,递归解析,直到value为普通值
value.then(resolve, reject);
}
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
setTimeout(() => {
// 放置定时器,是因为在promise2这个实例没有new完之前是undefined
try {
// promise的executor使用了try...catch...,这里又使用一次是因为定时器里面是异步代码
// 不能捕获错误,所以追加一个try...catch...
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
// todo...
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
// todo...
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
});
return promise2;
}
}
const promise = new Promise((resolve, reject) => {
resolve('resolved');
});
const promise2 = promise.then(data => {
console.log(data); // resolved
return new Promise((resolve, reject) => {
// resolve('yes');
reject('no');
});
});
promise2.then(
y => {
console.log('success', y);
},
e => {
console.log('failed', e);
}
);
Promise的透传以及我们then方法的进一步优化
在ES6 Promise中调用then方法的时候,resolve或者reject的值,是可以跟随着then方法往下传递的,比如:
const p = new Promise((resolve, reject)=> {
resolve(100);
// reject(200);
})
p.then().then().then(
res=> {
console.log(res);
},
err= {
consle.log(err);
}
)
// 相当于
p
.then(res=> {
return res;
})
.then(res=> {
return res;
})
.then(res=> {
console.log(res);
})
// 或者
p
.then(null, err=> {
throw err;
})
.then(null, err=> {
throw err;
})
.then(
res=> {},
err=> {
console.log(err);
}
)
所以,我们需要对promise的then方法中的回调函数追加一层判断
// ...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => throw err;
// ...
}
至此,我们就根据Promise A+规范实现了一个Promise
Promise的测试
Promise A+规范只规范了then方法的实现,还有catch方法,race方法,all方法,finally方法不属于A+规范,我将会在下一篇文章中,实现这些方法。
首先,安装测试包
npm install promises-aplus-tests
其次,根据测试包的说明,我们需要给Promise添加一个方法,方便测试
// ... 上面Promise代码
Promise.defer = Promise.deferred = function() {
const dfd = {};
dfd.promise = new Promise((resolve, reject)=> {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.export = Promise;
最后,测试我们的Promise代码
// 假如你的Promise文件名为promise.js
// 切换文件所在目录,执行
promises-aplus-tests promise.js
// 静静的等待结果就好了