关于Promise的文章那么多,内容或许差不多,那我为什么还要写一篇?两个原因:一是为了让自己更深入学习;二是将自己学习的过程记录下来。下面的内容就是按照Promises/A+规范自己手写一遍代码,包括常用的几个Promise方法。如果读者认为自己手写一个Promise完全没问题,那就没有必要浪费时间;如果感觉掌握的马马虎虎,我倒建议继续往下看看。
tip: 预计看完整片文章3-5分钟
Promise在解决什么问题?
- 有个词叫“回调地狱”,没错,主要解决的就是这个问题。直接看下面的例子。
// 一般回调写法
api.get('https://test.com/person?name="equicy"', (data) => {
if (data.code === 0) {
api.get('https://test.com/class?id='+data.id, (data) => {
if (data.code === 0) {
api.get('https://test.com/school?id='+data.id, (data) => {
console.log(data)
})
}
})
}
})
// Promise写法
function request(url) {
return new Promise((resolve, reject) => {
api.get(url, (data) => {
if (data.code === 0) {
resolve(data)
} else {
reject('出错啦')
}
})
})
}
request('https://test.com/person?name="equicy").then(data => {
return request('https://test.com/class?id='+data.id)
}).then(data => {
return request('https://test.com/school?id='+data.id)
}).then(data => {
console.log(data)
}).catch(err => {
console.log(err)
})
Promise是什么?
Promise的写法有很多,只要满足Promises/A+这个规范,就可以叫做Promise。因为这个规范是英文的,我依照自己认为的重点和自身的理解用中文简单翻译一下如果看不懂也没有关系,因为这里的每一条,都会在接下来的手写代码中有所体现
一. promise的状态
- promise总共有三种状态:pending(待定)、fulfilled(已实现)、rejected(已拒绝)
- 当前状态为pending时,可转换为fulfilled或者rejected
- 当前状态为fulfilled时,必须要返回一个值,且状态不可改变
- 当前状态为rejected时,必须要返回一个原因,且状态不可改变
二. then方法
- 一个promise必须要提供一个then的方法来接收当前的返回值
- then可以有两个可选参数(onFulfilled, onRejected)
- onFulfilled若存在,则为函数形式;若为普通值,则将其转换为函数(_=>_)
- onRejected若存在,则为函数形式;若为普通值,则将其转换为抛出异常的函数(err=>{throw err})
- 在当前执行上下文中不能执行onFulfilled和onRejected,换句话说,需要异步执行这两个函数,例如在宏任务setTimeout、setImmediate或者微任务mutationObserver中执行。
- 对于then,可被多次调用
- 当fulfilled后,所有onFulfilled回调都必须按其注册顺序执行
- 当rejected后,所有onRejected回调都必须按其注册顺序执行
- then必须返回的是一个promise
三. promise的处理过程在接下来的代码表现为函数resolvePromise
- promise的处理过程需要一个promise2和一个值x,x为回调函数onFulfilled或onRejected的执行结果。promise2则为返回的新promise,我们暂且表示为[[Resolve]](promise, x)
- 运行[[Resolve]](promise, x)需要满足的条件
- 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误
- 如果x不是一个函数或者对象,则以x为值执行promise, --resolve(x)
- 如果x是一个函数或者对象,将x.then赋予新的变量then。let then=x.then
- 将x作为this,执行then。
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y),此时递归执行,解决多次调用then的情况
- 如果 rejectPromise 以拒绝原因r为参数被调用,则以拒绝原因r拒绝 promise
遵循规范自己手写一个Promise
接下来就依照上面提到的规范来自己实现一个promise
// 首先定义三种状态为三个常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 定义一个promise类
class Promise {
// 该构造函数接收一个执行器(函数executor),executor需要在实例化的时候立即执行。
constructor(executor) {
this.status = PENDING // 默认状态为待定
this.value = undefined // 成功的返回值
this.reason = undefined // 失败的原因
let resolve = (val) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = val
}
}
let reject = (error) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = error
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === 'rejected') {
onRejected(this.reason);
};
}
看着似乎没什么问题,但是如果再加点料。像这样,就会有问题
const promise = new Promise((resolve, reject) => {
setTimeout(() =>{
resolve(1)
})
})
promise.then((value) =>{
console.log(value) // '',没有输出任何结果
})
考虑异步的情况
- 接下来就需要解决这种情况,也就是考虑异步的情况。也就是用两个数组(onResolveCallbacks,onRejectCallbacks)来收集回调,当执行到下一个tick的时候(事件循环机制),依次触发收集的回调函数(发布订阅)。
class Promise {
// 该构造函数接收一个执行器(函数executor),executor需要在实例化的时候立即执行。
constructor(executor) {
this.status = PENDING // 默认状态为待定
this.value = undefined // 成功的返回值
this.reason = undefined // 失败的原因
this.onResolveCallbacks = [] // 成功的回调函数集合
this.onRejectCallbacks = [] // 失败的回调函数集合
let resolve = (val) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = val
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (error) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = error
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === 'rejected') {
onRejected(this.reason);
};
if (this.status === PENDING) {
/*
* 异步并且多次调用的情况
* p = new Promise((resolve, reject) => {
* setTimeout(() =>{
* resolve(1)
* })
* })
* p.then()
* p.then()
*/
this.onResolveCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectCallbacks.push(() => {
onRejected(this.reason)
})
}
}
解决链式调用的问题
- 似乎promise允许这样使用,promise.then().then().then()
- 所以规定then函数返回的仍然是一个promise,我们称为promise2
- 规定then的两个参数如果不是函数则需要将其转为函数形式
- 第一个then返回的值中叫做x,我们用函数resolvePromise来判断x
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : _ => _
onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err }
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === PENDING) {
this.onResolveCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
接下来就是对函数resolvePromise的实现
- 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误
- 如果x不是一个函数或者对象,则以x为值执行promise, --resolve(x)
- 如果x是一个函数或者对象,将x.then赋予新的变量then。let then=x.then
- 将x作为this,执行then。
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y),此时递归执行,解决多次调用then的情况
- 如果 rejectPromise 以拒绝原因r为参数被调用,则以拒绝原因r拒绝 promise
- 下面就将这些规范应用到下面的代码中
function resolvePromise(promise2, x, resolve, reject) {
// 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误
if (promise2 === x) {
return reject(new TypeError('A promise cannot be resolved with itself')) // 禁止循环引用
}
let called;
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
// 将x作为this,执行then。
then.call(x, y => {
if(called) return;
called = true;
resolvePromise(promise2, y, resolve, reject)
}, r => {
if(called) return;
called = true;
reject(r)
})
} else {
resolve(x)
}
} catch (error) {
if(called) return;
called = true;
reject(error)
}
} else {
resolve(x)
}
}
到这里算是大功告成,下面再实现几个常用的方法
手写Promise的几个常用方法
// promise.resolve 直接返回成功态
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//promise.reject方法 直接返回失败态
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
// promise.all 所有成功才能返回成功的数组,有一个失败,则返回失败
Promise.all = function(promises) {
return new Promise(function(resolve, reject) {
var result = []
var count = 0
var promiseNum = promises.length
if (promises.length === 0) resolve([])
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(function(val) {
count++
result[i] = val
// 这样可以保证只有全部请求完成,才能返回所有的结果
if (count === promiseNum) resolve(result)
}, function(error){
reject(error)
})
}
})
}
// promise.race 一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。
Promise.race = function(promises) {
return new Promise(function(resolve, reject){
for (var i; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function(data){
resolve(data)
}, function(error) {
reject(error)
})
}
})
}
// promise.allSettled 返回所有的失败和成功的状态,并含有状态的对象
// allSettled 结果示例
// { status: 'fulfilled', data: 3 },
// { status: 'rejected', err: 'foo' }
Promise.allSettleds = function(promises) {
var res = []
var count = 0
return new Promise((resolve, reject) => {
for (let i=0; i {
res[i] = data
if (count === promises.length-1) {
resolve(res)
}
count++
}).catch(err=>{
res[i] = err
if (count === promises.length-1) {
resolve(res)
}
count++
})
}
})
}