还是有必要重学一次Promise
共 20044字,需浏览 41分钟
·
2021-03-06 23:33
❝关于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总共有三种状态: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<promises.length; i++) {
promises[i].then((data)=> {
res[i] = data
if (count === promises.length-1) {
resolve(res)
}
count++
}).catch(err=>{
res[i] = err
if (count === promises.length-1) {
resolve(res)
}
count++
})
}
})
}