Promise
什么是Promise
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大。ES6 将其写进了语言标准,统一了用法,提供了原生Promise对象 。
- 指定回调函数方式更灵活易懂。
- 解决异步 回调地狱 的问题。
为什么要使用Promise
使用promise主要是为了解决回调地狱,当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构,当嵌套的多了就会出现回调地狱的情况,比如我们发送三个 ajax 请求
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
ajax({
url: '我是第一个请求',
success (res) {
// 现在发送第二个请求
ajax({
url: '我是第二个请求',
data: { a: res.a, b: res.b },
success (res2) {
// 进行第三个请求
ajax({
url: '我是第三个请求',
data: { a: res2.a, b: res2.b },
success (res3) {
console.log(res3)
}
})
}
})
}
})
回调地狱,其实就是回调函数嵌套过多导致的
当代码成为这个结构以后,已经没有维护的可能了
Promise的基本使用
创建promise对象
在调用构造函数创建promise对象的时候会传入一个执行器函数,执行器函数中会发起IO请求,并会根据请求发出的情况调用resolve和reject方法来设置promise对象的状态。请求发生成功则会调用resolve方法将promise对象设置为成功状态,请求发生失败会调用reject方法将promise对象设置为失败状态
const promise = new Promise((resolve, reject) => {
// 若销售额达到
// resolve()
// 若销售额没有达到
reject()
})
根据状态处理请求
上面讲到,在执行器函数执行后,promise状态发生变化,此时可以调用promise的then方法来根据请求发出情况进行处理。在then方法中会传入两个回调函数,第一个回调函数用于处理执行成功时的情况,第二个回调函数用于处理执行失败时的情况。也就是说then方法既进行成功时的处理也可以进行失败时的处理,也就是如下形式:
promise.then(
() => {
console.log('发奖金')
},
() => {
console.log('不发奖金')
}
)
当然也可以不在then方法中处理失败,promise中提供了一个catch方法专门来处理失败,所以通常仅用then来处理成功的情况用catch来处理失败的情况,也就是如下形式:
promise.then(()=>{
console.log('发奖金')
}).catch(() => {
console.log('不发奖金')
})
Tips:若同时在then和catch中进行了失败的处理,会优先then,并且then方法和catch方法调用后又会返回一个promise对象
通过以上案例我们就可以知道了,promise将发生请求的过程和请求结果的处理过程分开了。请求处理完全是依据promise状态来进行的,所以我们有异步请求时只需要发生请求并根据请求结果设置promise状态并返回promise对象就可以了,后序的操作根据promise的状态来处理就行。也就是将异步请求结果的处理延迟到promise状态变化时
function myajax() {
return new Promise((resolve, reject) => {
// 发送io请求
// 发送成功
resolve()
// 发送失败
reject()
})
}
myajax().then().catch()
Promise的状态
三种状态
前面说到,Promise实现了将发生请求和请求结果处理分开。请求结果的处理是根据Promise 对象的状态来进行的。Promise有三种状态
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
当Promise对象创建出来时Promise默认为pending状态,如下可见:
const promise = new Promise((resolve, reject) => {})
console.log(promise)
状态的转变
Promise三种状态的变化途径只有两个
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
- 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
const promise = new Promise((resolve, reject) => {
resolve()
})
console.log(promise)
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
const promise = new Promise((resolve, reject) => {
reject()
})
console.log(promise)
Promise的结果
Promise除了可以通过自身状态来表现请求发送的状态以外还可以保存请求发送后的结果
发送成功的结果
resolve方法还可以接受响应结果作为参数。这里模拟ajax发送请求后将收到的响应作为resolve的参数。这个参数可以在then的处理成功的回调函数中获得
const promise = new Promise((resolve, reject) => {
// 发送io请求成功
resolve({ data: [], msg: '', code: '' })
})
promise
.then(res => {
// {data: Array(0), msg: '', code: ''}
console.log(res)
})
.catch(error => {
console.log(error)
})
发送失败的结果
当请求失败时也可以调用reject方法将失败的信息保存到Promise对象中,并在then的第二个回调函数或catch的回调函数中获得,这里只用catch做演示
const promise = new Promise((resolve, reject) => {
// 发送io请求成功
reject({ msg: '数据请求失败', code: 401 })
})
promise
.then(res => {
console.log(res)
})
.catch(error => {
// {msg: '数据请求失败', code: 401}
console.log(error)
})
封装ajax
封装ajax主要就是利用ajax发生请求并根据请求发送情况返回一个成功或失败状态的promise对象,然后根据promise对象处理请求
function ajax(url) {
// 若路径不对返回rejected状态的promise对象
if (url === '' || url !== './1.json') {
throw new Error('路径不合法')
// return Promise.reject()
}
// 获取缓存
let cache = ajax.cache || (ajax.cache = { data: null })
// 判定缓存中有是否有数据,有数据从缓存中获取数据
if (cache.data) {
console.log('走缓存')
// 返回promise对象
return Promise.resolve(cache.data)
}
// 无数据请求数据
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText))
// 将数据缓存
ajax.cache.data = xhr.responseText
} else {
reject(xhr.responseText)
}
}
}
})
}
let promise = ajax('./1.json')
promise
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
Promise对象方法
Promise.resolve
将现有数据(无论什么类型)包装为fulfilled状态的 Promise 对象
// 调用原型上的resolve方法
const p1 = Promise.resolve('lmh')
// 等价于
const p2 = new Promise(resolve => resolve('lmh'))
console.log(p1)
console.log(p2)
应用场景:为ajax添加缓存功能
当使用单页面应用时,为了避免切换页面而导致频繁请求后台数据,所以给ajax加上缓存功能,当缓存中有数据从缓存中获取数据。当然对于缓存的数据依然以promise对象形式返回,这样可以避免数据处理的差异,这里就需要用到resolve方法了
function ajax(url) {
// ..
}
setTimeout(() => {
let promise = ajax('./1.json')
promise
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
}, 1000)
setTimeout(() => {
let promise = ajax('./1.json')
promise
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
}, 2000)
Promise.reject
将现有数据(无论什么类型)包装为rejected状态的 Promise 对象
const p1 = new Promise((resolve, reject) => {
resolve()
})
const p2 = Promise.reject(p1)
console.log(p1)
console.log(p2)
应用场景:
let promise = ajax('./1.json')
promise
.then(res => {
// 虽然数据请求成功,单数数据是空的,也就是说实
if (res) {
console.log('渲染数据')
} else {
// 返回一个错误状态的promise对象
return Promise.reject(new Error('出错了'))
// 或者throw new Error("出错了")
}
})
.catch(error => {
console.log(error)
})
then和catch的链式调用
promise调用then方法或者catch后会返回一个promise对象,这样就可以进行链式调用了,从而解决回调地狱
调用then时若第一个回调函数返回的是字符串或者fulfilled状态的promise对象的时候,那么then方法调用后将返回fulfilled状态的promise对象,并且promise携带的数据就是第一个回调函数的返回值
const p1 = new Promise((resolve, reject) => {
resolve('data')
})
const p2 = p1
.then(res => {
console.log(res)
// 返回res
return res
})
.catch(err => {
console.log(error)
})
console.log(p2)
若直接返回rejected状态的Promise则then方法调用后也返回rejected状态的Promise
const p1 = new Promise((resolve, reject) => {
resolve('data')
})
const p2 = p1
.then(res => {
console.log(res)
// 返回res
return Promise.reject(res)
})
.catch(err => {
console.log(error)
})
console.log(p2)
链式调用案例
function ajax(url) {
// ..
}
let name = 'kerwin'
ajax(`http://localhost:3000/news?author=${name}`)
.then(data => {
const id = data[0].id
return ajax(`http://localhost:3000/commons?newsId=${id}`)
})
.then(data => {
console.log(data)
})
.catch(error => {
console.log(error)
})
Promise.all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);其中p1,p2,p3都是promise对象
p的状态由p1,p2,p3 决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,那么调用Promise.all()方法后返回的p的状态才会变成fulfilled,此时p1、p2、p3的返回值会组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
function ajax(url) {
//...
}
let list = ['kerwin', 'tiechui']
// 将list中的每个元素映射为promise对象,并放入新数组
let promiseList = list.map(item => ajax(`http://localhost:3000/news?author=${item}`))
// Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
let promises = Promise.all(promiseList)
promises
.then(dataList => {
console.log(dataList)
})
.catch(error => {
console.log(error)
})
提示:若期约数组中其中一个不是期约对象,那么它就会被当作一个已兑现的期约值,被原封不动的复制到输出数组中
Promise.race
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
实际应用:
为ajax设置请求超时,当发起请求是同时设定一个定时器,若在定时器指定时间没有收到响应那么表示请求超时返回失败的promise
function ajax(url, time = 1000) {
// 若路径不对返回rejected状态的promise对象
if (url === '') {
throw new Error('路径不合法')
// return Promise.reject()
}
// 获取缓存
let cache = ajax.cache || (ajax.cache = { data: null })
// 判定缓存中有是否有数据,有数据从缓存中获取数据
if (cache.data) {
// 返回promise对象
return Promise.resolve(cache.data)
}
// 无数据请求数据
const p1 = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText))
// 将数据缓存
ajax.cache.data = xhr.responseText
} else {
reject(xhr.responseText)
}
}
}
})
// 设置定时器,若发出请求时间超过定时器设定时间那么返回失败状态的promise,表示请求超时
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('超时')
}, time)
})
return Promise.race([p1, p2])
}
ajax('./test.json', 1)
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
应用二:可以同时请求多态服务器,当一台挂掉的时候会返回另一请求成功的promise
Promise.allSettled
在前面的Promise.all()方法中,当有一个promise对象的状态变为rejected时,都会走catch方法,这时候其它fulfilled状态的promise对象也依然要进入catch方法中进行处理,而我们希望当所有异步任务结束后的promise都进入then方法进行处理这时就需要用到Promise.allSettled方法了
Promise.allSettled()方法(ES13的),用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做“Settled”(落地),包含了“fulfilled”和“rejected”两种情况。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 2000)
})
const promises = [p1, p2]
Promise.allSettled(promises)
.then(res => {
// 过滤出成功的promise的数据
successList = res.filter(item => item.status === 'fulfilled')
// 过滤出失败的promise的数据
failList = res.filter(item => item.status === 'rejected')
})
.catch(error => {
console.log(error)
})
注意:若对象status为'fulfilled' 那么还有一个value属性保存着兑现的值,若tatus为'rejected',那么对象中还有一个值为reason保存着拒绝的理由
Promise.any
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
应用场景
假如登录一个会员联盟系统,这时候登录会员联盟的任意一个成功都算成功,那么就算成功,比如腾讯视频、爱奇艺、酷米视频三家会员联盟登录成功一个其它都算登录成功
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('腾讯视频登录失败')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('爱奇艺视频登录成功')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('酷米视频登录失败')
}, 3000)
})
const promises = [p1, p2, p3]
Promise.any(promises)
.then(res => {
console.log('登录成功')
// 跳转主页
})
.catch(error => {
console.log('登录失败')
})
Promise.finally
finally方法无论promise什么状态都会执行(ES9中的新特性)
应用场景:通常在这个方法中释放资源,比如发出请求后显示加载器,等数据回来后隐藏加载器就可以在finally中进行
const promise = new Promise((resolve, reject) => {
console.log('loading中')
setTimeout(() => {
reject('请求数据')
}, 1000)
})
promise
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
.finally(() => {
console.log('隐藏loading')
})
async和await
async的使用
什么是async
async是用来声明函数的关键字,被async关键字声明的函数表示是一个异步函数其内部可能有异步操作,并且使用了该关键字的函数该一定会返回一个Promise实例对象。这样就不用我们new Promise了
async函数的返回值
async函数一定会返回promise对象但是返回的promise对象的状态应场景而不同
情形一:若函数无返回值,且函数执行没有错误,则函数执行后会默认返回一个不带数据的fulfilled状态的promise对象
async function test() {}
let res = test()
console.log(res) // 没有携带数据的promise
情形二:若函数无返回值,且函数有执行错误或手动抛出了错误,那么返回的是rejected状态的Promise对象
async function test() {
throw new Error('手动抛出错误')
}
let res = test()
console.log(res) // 带有错误信息的rejected状态的promise
情形三:若有返回值,且返回字符串等基本类型数据则实际返回以该字符串等基本类型数据为数据的fulfilled状态的promise对象
async function test() {
return 'test'
}
let res = test()
console.log(res) // 以该字符串为数据的fulfilled状态的promise对象
情形四:若有返回值,且返回fulfilled状态的promise对象那么就是这个对象
async function test() {
return Promise.resolve('hello')
}
let res = test()
console.log(res)
情形五:若有返回值,且返回rejected状态的Promise对象那么就是这个对象
async function test() {
return Promise.reject(new Error('error'))
}
let res = test()
console.log(res)
await的使用
什么是await
await与async一样都是关键字,像其语义“等待”一样,await要求我们代码的执行时需要等待上一步异步操作执行完成才能执行下一个异步操作。当然await的作用不仅于此,它可以类似于then方法一样获取promise的结果值
await解构promise对象结果值
解构成功状态的promise结果值
await的作用之一在于解构成功状态下的promise对象,使我们能够直接拿到成功状态下的promise对象中的结果值,可以把它视为then函数的语法糖,方便我们的取值操作,如下案例:
若不使用await操作获取成功状态的promise对象值,则需要在获取promise对象后又调用其then方法,在then方法的回调函数中获取promise对象的结果值
async function demo() {
const promise = new Promise((resolve, reject) => {
resolve('数据')
})
promise
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
}
demo()
而使用await获取promise对象值时就不需要这么麻烦了,我们可以直接获取fulfilled状态的promise对象的结果值
async function demo() {
const data = await new Promise((resolve,reject) => {
resolve('数据')
})
console.log(data) // 数据
}
demo();
对失败状态promise对象的处理
若await后面的异步操作返回失败状态的promise对象,那么await会抛出错误来表示异步操作执行失败,我们可以使用try/catch来捕获处理异常,如下案例:
async function demo() {
try {
const data = await new Promise((resolve, reject) => {
reject(new Error('出错了'))
})
} catch (error) {
console.log(error)
}
}
demo()
非promise对象的处理
当然若await后面非异步操作,而是数值则直接获取值,如下案例:
async function demo(){
const data = await 100
console.log(data); // 100
}
await的阻塞作用
不使用await的代码执行顺序
await也是一个关键字,就如同其语义一样,就是要等待异步执行结果。在没有使用await时,若遇到同步代码和异步代码同时出现,一定是先执行同步,再执行异步,比如下面的案例:
function demo() {
console.log('111')
setTimeout(() => {
console.log('异步函数执行了')
}, 3000)
console.log('333')
}
demo()
console.log('222')
执行结果为:
可以看到上面的代码会将demo中的同步代码执行完成后,然后打印'222',最后再执行异步代码打印'异步函数执行了',这样的后果就是我们不能够根据上一个异步操作的结果来进行后序异步操作
使用await的代码执行顺序
若是使用了await,就像是其语义“等待”一样,await后面的代码若是异步操作,则会等待这一步异步操作完毕后再执行下面的语句,也就是将demo函数的执行进行阻塞,转而执行打印了“222”后,等到异步操作完成再继续执行demo函数执行,案例如下:
async function demo() {
console.log('111')
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
console.log('异步函数执行了')
}, 3000)
})
console.log('333')
}
demo()
console.log('222')
总结:就是当函数出现异步代码时,若不使用await那么函数中的异步代码就会被跳过,执行函数剩下的代码,若使用了await那么函数内部就会等待函数中的异步代码执行完成后再执行函数中的剩余代码
async和await的结合使用
不使用async和await
就是将回调地狱变为了链式写法
function ajax(url, time = 1000) {
//...
}
let name = 'kerwin'
ajax(`http://localhost:3000/news?author=${name}`)
.then(res => {
const id = res[0].id
return ajax(`http://localhost:3000/commons?newsId=${id}`)
})
.then(res => {
console.log(res)
})
.catch(error => {
console.log(error)
})
使用async和await
这个就是类似于上面调用的语法糖
async function getData() {
let name = 'kerwin'
try {
// 等待该异步操作执行完成后在执行后序语句
const res = await ajax(`http://localhost:3000/news?author=${name}`)
const id = res[0].id
const data = await ajax(`http://localhost:3000/commons?newsId=${id}`)
console.log(data)
} catch (error) {
console.log(error)
}
}
注意:await通常与aysnc来一起使用,但是用async声明的函数内部不一定要有await,反之函数内部有await则函数必须要用async声明
手写Promise
promise的几个特点
promise是一个拥有then方法的对象或者函数:then方法相当于观察者模式中的订阅,所以这就是为什么then需要传入函数。期约就是一个被观察者,then方法的第一个参数相当于第一个观察者,当期约状态转为fulfilled的时候调用,第二个参数相当于第二个观察者,当期约的状态变为rejected的时候被调用promise有三种状态,只能从等待状态到成功或者失败():为什么要有三种状态?防止多次调用,因为你如果调用两次resolve,那么只能以第一次的为准,第一次resolve后,状态直接就变为SUCCESS,那么也就不会在执行后面的reslove了then方法返回一个新的promise对象:为什么要返回promise,为了支持链式调用promise初始化传入一个函数,函数上有两个参数,resolve,reject,他们也都是函数promise中的resolve,reject也就相当于观察者模式中的notify
promise基本实现
这里最难的地方在于then方法,以及处理then方法中回调函数返回值的问题。下面是then方法的一些详细描述
上一个期约落地后,就会执行then方法的回调函数,但是这里执行回调函数是在then方法返回的新期约对象中的执行器中执行的。在执行器中具体执行哪一个回调则是受上一个期约落地时状态的影响,若上一个期约兑现则调用onFulfilledCallback(),若上一个期约拒绝则调用onRejectedCallback()。这些回调函数的返回值最终将会作为新期约对象的值,但是注意并不是返回什么值就将什么值作为新期约对象的值,期约对象应该只接受非promise类型的值。这里有几种情况
- 若返回的值是非期约类型,则表示执行(解决过程顺利),新期约也兑现,并直接使用这个新期约的值作为兑现后的值
- 若返回的值是兑现的期约。则表示新期约的执行(解决)很顺利,最终新期约也兑现
- 若返回的值是拒绝的期约,则表示新期约的执行(解决)不顺利,在执行过程中发生可以外,最终新期约也拒绝
- 若返回的值是嵌套期约,这时候就要递归的解析,直到解析出非promise类型值,并且在递归过程发现有拒绝状态的promise则直接设置新期约为拒绝状态。
总之一定要明白,期约的解决过程不等于期约兑现,要期约的解决过程中产生的期约都兑现新期约才会兑现,若有一个解决环节失败则新期约一定被拒绝。
/**
* 期约状态枚举
*/
enum Status {
pending = 'pending',
fulfilled = 'fulfilled',
rejected = 'rejected'
}
const isFunction = (value: any) => typeof value === 'function'
/**
* 期约类
*/
class MyPromise {
// 期约状态,初始时为'pending',
private _status: string = Status.pending
// 期约兑现后的值
private _value: any = ''
// 拒绝后的原因
private _reason: any = ''
// 期约兑现后的回调队列
private _onFulfilledCallbackList: Array<Function> = []
// 期约拒绝后的回调
private _onRejectedCallbackList: Array<Function> = []
/**
* @param executor 执行器函数
* @description 期约构造器
*/
constructor(executor: Function) {
// 兑现期约函数
let resolve = (value: any) => {
if (this._status === Status.pending) {
this._status = Status.fulfilled
this._value = value
// 期约兑现,状态改变,立即执行兑现后的回调队列中的回调函数
this._onFulfilledCallbackList.forEach(fn => fn())
}
}
// 拒绝期约函数
let reject = (reason: any) => {
if (this._status === Status.pending) {
this._status = Status.rejected
this._reason = reason
// 期约被拒绝,状态改变,立即执行拒绝后的回调队列中的回调函数
this._onRejectedCallbackList.forEach(fn => fn())
}
}
// 执行执行器函数
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
/**
* @param onFulfilledCallback 期约状态观察者函数,当期约状态变为fulfilled时调用该方法
* @param onRejectedCallback 期约状态观察者函数,当期约状态变为rejecteded时调用该方法
* @returns newPromise:MyPomise 新的Promise对象
* @description 期约落地时的回调
*/
then(onFulfilledCallback?: Function, onRejectedCallback?: Function) {
// onFulfilledCallback,onRejectedCallback都是可选参数
onFulfilledCallback = isFunction(onFulfilledCallback) ? onFulfilledCallback : (data: any) => data
onRejectedCallback = isFunction(onRejectedCallback) ? onRejectedCallback : (error: any) => {
throw error
}
let newPromise = new MyPromise((resolve: Function, reject: Function) => {
// 执行回调后返回的值
let result: any
// 期约已经兑现,执行兑现时的回调函数(上个期约执行器函数中是同步代码)
if (this._status === Status.fulfilled) {
setTimeout(() => {
try {
// 设置为回调方式,等待newPromise对象创建完成后再进行解析
onFulfilledCallback && (result = onFulfilledCallback(this._value))
// 解决新期约
this.resolvePromise(newPromise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 期约被拒绝,执行拒绝时的回调函数(上个期约执行器函数中是同步代码)
if (this._status === Status.rejected) {
setTimeout(() => {
try {
onRejectedCallback && (result = onRejectedCallback(this._reason))
// 解决新期约
this.resolvePromise(newPromise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 若在调用then方法时,期约的状态还未发生变化则存储回调函数直到状态变化时调用(执行器函数中是异步代码)
if (this._status === Status.pending) {
onFulfilledCallback && this._onFulfilledCallbackList.push(() => {
setTimeout(() => {
try {
result = onFulfilledCallback(this._value)
// 解决新期约
this.resolvePromise(newPromise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
onRejectedCallback && this._onRejectedCallbackList.push(() => {
setTimeout(() => {
try {
result = onRejectedCallback(this._reason)
// 解决新期约
this.resolvePromise(newPromise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return newPromise
}
/**
* @param newPromise then方法返回的新期约对象
* @param result 新期约执行过程中由上一个期约落地后的回调函数产生的值 对于promise类型的对象需要将值进行解构
* @param resolve 新期约的兑现函数
* @param reject 新期约的拒绝函数
* @description 解决期约(解构期约)
*/
resolvePromise(newPromise: MyPromise, result: any, resolve: Function, reject: Function) {
// 回调返回的值不能是当前创建的新期约
if (newPromise === result) {
throw new TypeError("Chaining cycle detected for promise #<MyPromise>")
}
// 若返回的是函数或对象类型则做进一步解析
if ((typeof result === 'object' && result !== null) || typeof result === 'function') {
let called: boolean = false
try {
// 获取then方法 预防取.then的时候错误 因为then方法可能是访问器属性,在访问的时候报错
let then = result.then
// 若then是一个方法,则表示返回的result可能是MyPromise对象
if (typeof then === 'function') {
// 调用then方法来将多层的promise解构,但是要注意then调用时又会重复调用 this.resolvePromise(newPromise, value, resolve, reject)方法,所以需要做一些处理
then.call(result, (value: any) => {
if (called) {
return
}
called = true
// 递归解析,防止嵌套MyPromise对象
this.resolvePromise(newPromise, value, resolve, reject)
}, (reason: any) => {
if (called) {
return
}
called = true
reject(reason)
})
} else {
if (called) {
return
}
called = true
// 若then不是一个方法,则result是普通对象或函数,直接以该值兑现新期约
resolve(result)
}
} catch (error) {
if (called) {
return
}
called = true
// 若then方法调用过程中出现了异常则直接拒绝新期约
reject(error)
}
} else {
// 非函数或对象类型则直接返回结果
resolve(result)
}
}
}
检测自己写的期约是否正确
将上面的代码使用tsc工具转为js
/**
* 期约状态枚举
*/
var Status;
(function (Status) {
Status["pending"] = "pending";
Status["fulfilled"] = "fulfilled";
Status["rejected"] = "rejected";
})(Status || (Status = {}));
var isFunction = function (value) { return typeof value === 'function'; };
/**
* 期约类
*/
var MyPromise = /** @class */ (function () {
/**
* @param executor 执行器函数
* @description 期约构造器
*/
function MyPromise(executor) {
var _this = this;
// 期约状态,初始时为'pending',
this._status = Status.pending;
// 期约兑现后的值
this._value = '';
// 拒绝后的原因
this._reason = '';
// 期约兑现后的回调队列
this._onFulfilledCallbackList = [];
// 期约拒绝后的回调
this._onRejectedCallbackList = [];
// 兑现期约函数
var resolve = function (value) {
if (_this._status === Status.pending) {
_this._status = Status.fulfilled;
_this._value = value;
// 期约兑现,状态改变,立即执行兑现后的回调队列中的回调函数
_this._onFulfilledCallbackList.forEach(function (fn) { return fn(); });
}
};
// 拒绝期约函数
var reject = function (reason) {
if (_this._status === Status.pending) {
_this._status = Status.rejected;
_this._reason = reason;
// 期约被拒绝,状态改变,立即执行拒绝后的回调队列中的回调函数
_this._onRejectedCallbackList.forEach(function (fn) { return fn(); });
}
};
// 执行执行器函数
try {
executor(resolve, reject);
}
catch (error) {
reject(error);
}
}
/**
* @param onFulfilledCallback 期约状态观察者函数,当期约状态变为fulfilled时调用该方法
* @param onRejectedCallback 期约状态观察者函数,当期约状态变为rejecteded时调用该方法
* @returns newPromise:MyPomise 新的Promise对象
* @description 期约落地时的回调
*/
MyPromise.prototype.then = function (onFulfilledCallback, onRejectedCallback) {
var _this = this;
// onFulfilledCallback,onRejectedCallback都是可选参数
onFulfilledCallback = isFunction(onFulfilledCallback) ? onFulfilledCallback : function (data) { return data; };
onRejectedCallback = isFunction(onRejectedCallback) ? onRejectedCallback : function (error) {
throw error;
};
var newPromise = new MyPromise(function (resolve, reject) {
// 执行回调后返回的值
var result;
// 期约已经兑现,执行兑现时的回调函数(上个期约执行器函数中是同步代码)
if (_this._status === Status.fulfilled) {
setTimeout(function () {
try {
// 设置为回调方式,等待newPromise对象创建完成后再进行解析
onFulfilledCallback && (result = onFulfilledCallback(_this._value));
// 解决新期约
_this.resolvePromise(newPromise, result, resolve, reject);
}
catch (error) {
reject(error);
}
}, 0);
}
// 期约被拒绝,执行拒绝时的回调函数(上个期约执行器函数中是同步代码)
if (_this._status === Status.rejected) {
setTimeout(function () {
try {
onRejectedCallback && (result = onRejectedCallback(_this._reason));
// 解决新期约
_this.resolvePromise(newPromise, result, resolve, reject);
}
catch (error) {
reject(error);
}
}, 0);
}
// 若在调用then方法时,期约的状态还未发生变化则存储回调函数直到状态变化时调用(执行器函数中是异步代码)
if (_this._status === Status.pending) {
onFulfilledCallback && _this._onFulfilledCallbackList.push(function () {
setTimeout(function () {
try {
result = onFulfilledCallback(_this._value);
// 解决新期约
_this.resolvePromise(newPromise, result, resolve, reject);
}
catch (error) {
reject(error);
}
}, 0);
});
onRejectedCallback && _this._onRejectedCallbackList.push(function () {
setTimeout(function () {
try {
result = onRejectedCallback(_this._reason);
// 解决新期约
_this.resolvePromise(newPromise, result, resolve, reject);
}
catch (error) {
reject(error);
}
}, 0);
});
}
});
return newPromise;
};
/**
* @param newPromise then方法返回的新期约对象
* @param result 新期约执行过程中由上一个期约落地后的回调函数产生的值 对于promise类型的对象需要将值进行解构
* @param resolve 新期约的兑现函数
* @param reject 新期约的拒绝函数
* @description 解决期约(解构期约)
*/
MyPromise.prototype.resolvePromise = function (newPromise, result, resolve, reject) {
var _this = this;
// 回调返回的值不能是当前创建的新期约
if (newPromise === result) {
throw new TypeError("Chaining cycle detected for promise #<MyPromise>");
}
// 若返回的是函数或对象类型则做进一步解析
if ((typeof result === 'object' && result !== null) || typeof result === 'function') {
var called_1 = false;
try {
// 获取then方法 预防取.then的时候错误 因为then方法可能是访问器属性,在访问的时候报错
var then = result.then;
// 若then是一个方法,则表示返回的result可能是MyPromise对象
if (typeof then === 'function') {
// 调用then方法来将多层的promise解构,但是要注意then调用时又会重复调用 this.resolvePromise(newPromise, value, resolve, reject)方法,所以需要做一些处理
then.call(result, function (value) {
if (called_1) {
return;
}
called_1 = true;
// 递归解析,防止嵌套MyPromise对象
_this.resolvePromise(newPromise, value, resolve, reject);
}, function (reason) {
if (called_1) {
return;
}
called_1 = true;
reject(reason);
});
}
else {
if (called_1) {
return;
}
called_1 = true;
// 若then不是一个方法,则result是普通对象或函数,直接以该值兑现新期约
resolve(result);
}
}
catch (error) {
if (called_1) {
return;
}
called_1 = true;
// 若then方法调用过程中出现了异常则直接拒绝新期约
reject(error);
}
}
else {
// 非函数或对象类型则直接返回结果
resolve(result);
}
};
return MyPromise;
}());
然后加上如下代码:
MyPromise.defer = MyPromise.deferred = function() {
let dfd = {}
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = MyPromise
用promises-aplus-tests插件来检测一下
安装:npm install promises-aplus-tests -g插件
执行命令:promises-aplus-tests promise.js
promise方法实现
/**
* @param onRejectedCallback 期约拒绝时的回调函数
* @description 解决期约拒绝函数
* @returns 在上一个拒绝期约状态下返回新的Promise
*/
public catch(onRejectedCallback: Function) {
return this.then(undefined, onRejectedCallback)
}
/**
* @param value 要检测的数据
* @description 检测传入参数是否为MyPromise类型
* @returns 是MyPromise对象则返回true 否则返回false
*/
public static isPromise = (value: any) => {
if ((value != null && typeof value === 'object') || typeof value === 'function') {
if (typeof value.then == 'function') {
return true
}
} else {
return false
}
}
/**
* @param lists 期约列表
* @description 并行处理期约
* @returns 当列表中一个期约为拒绝状态则返回拒绝状态的期约,若都是兑现状态则返回兑现状态的
*/
public all(lists: Array<any>) {
return new MyPromise((resolve: Function, reject: Function) => {
let results: Array<any> = []
let index = 0
function processData(i: number, data: any) {
results[i] = data
index += i
if (index == lists.length) {
// 处理异步,要使用计数器,不能使用resArr==lists.length
resolve(results)
}
}
// 循环获取列表中promise的结果
for (let i = 0; i < lists.length; i++) {
if (MyPromise.isPromise(lists[i])) {
lists[i].then((value: any) => {
processData(i, value)
}, (reason: any) => {
// 只要有一个传入的promise没执行成功就走reject
reject(reason)
return
})
} else {
// 普通数据直接作为期约兑现值
processData(i, lists[i])
}
}
})
}
/**
* @param lists 期约列表
* @description 获取状态改变最快的期约的值
* @returns 返回一个包含状态改变最快的期约对象的值的新期约
*/
public race(lists: Array<any>) {
return new MyPromise((resolve: Function, reject: Function) => {
for (let i = 0; i < lists.length; i++) {
if (MyPromise.isPromise(lists[i])) {
lists[i].then((value: any) => {
resolve(value)// 哪个先完成就返回哪一个的结果
return
}, (reason: any) => {
reject(reason)
return
})
} else {
resolve(lists[i])
}
}
})
}
/**
* @param value 给定值
* @description 返回以给的值的兑现期约
* @returns 兑现状态的期约
*/
static resolve(value: any) {
if (MyPromise.isPromise(value)) {
return value
} else {
return new MyPromise((resolve: Function, reject: Function) => {
resolve(value)
})
}
}
/**
* @param value 给定值
* @description 返回以给的值的拒绝期约
* @returns 拒绝状态的期约
*/
static reject(value: any) {
return new MyPromise((resolve: Function, reject: Function) => {
reject(value)
})
}
/**
* @param cb 回调函数
* @description 期约落地后再执行一个回调函数
* @returns 新期约对象
*/
public finally(cb: Function) {
return this.then(
(value: any) => MyPromise.resolve(cb()).then(() => value),
(reason: any) => MyPromise.reject(cb()).then(() => { throw reason })
)
}