前言
该章为个人ES6Promise知识点笔记,主要记录Promise的基本api和注意点,最后附上利用Class封装的简单Promise
内容
1. Promise基本介绍
- 什么是Promise?
Promise是ES6中新的语法,一种新的异步编程的解决方案;
使用上来说,Promise是一个构造函数,封装一个异步操作对象并可以获得其结果
注意:为什么要有新的解决方案,旧的解决方案是什么为什么不好
-
旧的解决方案是使用回调函数的方式,不利于阅读也容易产生回调地狱,callBack嵌套callBack
-
旧方案不够灵活,必须先指定回调函数,Promise可以先执行异步,异步结束后再指定回调
-
Promise的基本工作流程
Promise有三种状态 pending(初始)、resolved(成功)、rejected(失败)
const p1 = new Promise((resolve, reject) => {
//1. Promise在创建之初,此时Promise处理pending状态
setTimeout(()=>{
//3. 异步操作之后调用了resolve,此时Promise进入resolved状态
resolve(1)
},1000)
})
//2. 指定Promise的onResolved,onRejected回调函数
p1.then(
value=>{//onResolved函数,成功的回调
//4. Promise随着resolve的调用,改变为成功状态后,去异步调用对应的onResolved成功回调函数
console.log(value)
},
reason=>{//onRejected函数,失败的回调
console.log(reason)
}
)
2. Promise基础api
下面介绍Promsie最常用的基础api和展示
- new Promise() 需要传入一个执行器函数,返回一个Promise对象,根据执行器函数内部调用resolve或reject,从而改变状态,触发回调函数
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1)
},1000)
})
- then 传入两个回调函数,第一个是成功的回调函数,第二个是失败的回调函数,返回一个新的Promise对象,Promise根据其状态是成功还是失败从而选择调用这里的哪一个回调函数,返回的新Promise对象会根据回调函数的执行结果,从而决定自己的状态执行新一轮的回调,最终完成链式调用
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1)//这里调用了ressolve Promise会进入成功状态,会调用.then中传入的成功回调,反之调用reject就会调用失败的回调
},1000)
}).then(
//成功的回调
value=>{
console.log(value)//1
return 2 //这里返回了2那么链式调用时,下一个成功的回调参数就能接受到2
},
//失败的回调
reason=>{
}
).then(
value=>{
console.log(value)//2
throw Error(3)//当这里抛出了错误,或者返回一个失败状态的Promise,那么下一次就调用.then中失败的回调
},
reason=>{
}
)
.then(
value=>{
},
reason=>{
console.log(reason)//3 这里就调用了失败的回调
}
)
- catch 传入一个回调函数,当Promise在执行之前的回调中,发生了错误或者失败了,将执行这个回调,实际上就相当于.then的一个语法糖
//伪代码示意
.catch(reason=>{
//xxx
})
//等效于
.then(undefind,reason=>{
//xxxx
})
- all 接受一个数组,返回一个Promise对象,数组内可以是多个Promise对象或者是非Promise对象的数据(一般都为Promise对象,如果不是Promsie对象,那么默认是以该值返回一个成功的Promsie对象),当数组内所有Promise对象状态为成功那么返回的Promsie对象进入成功状态回调的参数是数组内Promise对象的成功回调参数,如果有一个失败了,那么返回的Promsie就会进入失败状态,回调的参数就是数组内第一个失败的Promise的原因
const p1 = new Promise((resolve, reject) => {
resolve(1)
})
const p2 = new Promise((resolve, reject) => {
resolve(2)
})
const p3 = new Promise((resolve, reject) => {
resolve(3)
})
Promise.all([p1, p2, p3])
.then(
value => { console.log(value)},//全部成功all进入成功回调,回调参数是[1,2,3]
reason => { console.log(reason) })
另一种情况
const p1 = new Promise((resolve, reject) => {
reject(1) //p1进入失败回调 回调的参数是1
})
const p2 = new Promise((resolve, reject) => {
resolve(2)
})
const p3 = new Promise((resolve, reject) => {
resolve(3)
})
Promise.all([p1, p2, p3])
.then(
value => { console.log(value)},
reason => { console.log(reason) })//没有全部成功all进入失败回调,reason=1
- race 接受一个数组,返回一个Promise对象,数组内可以是多个Promise对象或者是非Promise对象的数据(一般都为Promise对象,如果不是Promsie对象,那么默认是以该值返回一个成功的Promsie对象),根据数组内第一个进入回调的Promise进行判断,该Promise的状态就是返回Promsie的状态,该Promsie的回调函数的参数就是返回Promise回调函数的参数
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resovle(1) //最先进入回调 成功回调 回调参数为1
}, 1000);
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resovle(2)
}, 2000);
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resovle(3)
}, 3000);
})
Promise.race([p1, p2, p3])
.then(
value => { console.log(value)},//成功回调 回调参数为1
reason => { console.log(reason) })
- resolve、reject
接受一个Promise或者一个非Promise对象的值
a. 调用resolve返回一个Promise,如果传入非Promsie对象数据,那么返回一个成功的Promsie,成功的回调参数就是传入的非Promise对象数据,如果传入的是一个Promise对象,那么返回的Promise根据传入的Promise的状态和回调参数,决定自己的状态和回调参数
b. 调用reject返回一个失败的Promise,失败回调的参数就是,你传入的参数
Promise.resolve(1) //返回成功Promise回调参数是1
Promise.reject(2) //返回失败的Promise回调参数是2
Promise.resolve(Promise.resolve(1)) //返回成功的Promise回调参数是1
Promise.resolve(Promise.reject(2)) //返回失败的Promise回调参数是2
3. 中断Promise链
有些情况下我们需要中断Promise.then()的回调链,虽然很少见,但是笔者在封装axios拦截器取消请求的时候确实遇到了这样的情况,先贴上代码
... //这里就用伪代码示意了
.then(
value=>{
return new Promise(()=>{}) //返回一个初始状态的Promise就行了
},
reason=>{
return new Promise(()=>{})//返回一个初始状态的Promise就行了
}
)
.then(
//后续的.then不会执行了
)
原理:.then时会返回一个Promise,在这个Promise中我们需要去调用reject或者resolve去改变Promise的状态触发回调函数,用来完成链式调用,但是如果是返回了一个初始状态的Promsie,没有调用reject或者resolve,那么后续的.then都会去等待这个Promsie去改变状态从而触发回调函数,从而达到中断Promsie;
4. async/await和Promise
简介
- async/await被称为最终的处理异步回调的方案,通常和Promise配套使用,以下展示
//模拟请求函数
function getMessage(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(111)//假设1秒后请求到了数据111
},1000)
})
}
//在另一个函数中我们需要使用到请求下来的数据,以往我们需要在.then中处理
async function fn1 (){
const data = await getMessage() //data=111 这行代码是同步执行的,此时data=111
}
以上为async/await的基本使用,一般而言最常见的场景就是这样,async/await的使用使得我们不必频繁的用.then处理回调,编码上可以使用同步的方式编码也能去处理异步的回调,但是有以下几点需要稍微注意
注意
-
async/await 一般而言是配套使用,但是也不是必须的,async可以单独使用在函数前面,函数内不必一定使用await,但是反之不行,如果使用await那么函数前必须带上async,否则会报错
-
async 用于函数前时,这个函数将不再返回原有的值,函数将直接返回一个Promise,这个Promise的状态由原函数的返回值决定,大致的返回规则如下: a. 如果原函数返回了一个非Promise的值,那么async将会使得函数返回一个成功的Promise,成功回调的值就是原函数的返回值
b. 如果原函数返回了一个Promise值,那么async将会使得函数返回一个Promise,这个Promise的状态和值由返回的Promise决定
c. 如果函数执行报错,那么async将会使得函数返回一个失败的Promise,这个Promise的值就是报错的原因 -
await的后面一般是个表达式或Promise对象,但是也可以不是,如果是Promise对象,await就会获取Promise对象回调后的结果,如果不是那么就直接返回原值
5. 手动封装Promise
最后是手动封装一个简易的Promise,这个也是前端初级进阶需要能够手撕的代码之一,笔者的Promise是利用ES6的Class封装的,实现了简单的构造函数、then、catch、reject、resolve、all、race这几个方法,方法内贴出了部分注释仅供参考
(function (window) {
//利用闭包创建promise的三种基础状态
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
//Promise类
class Promise {
//Promise构建时需要传入一个执行器函数
constructor(excutor) {
this.status = PENDING;//初始状态为PENDING
this.data = undefined;//初始时数据不会被定义,这个需要在调用回调函数时被赋值
this.callBacks = []; //[{onResolved ,onRejected}] 回调队列
//1. 封装一个handleCallBack函数用于处理进入不同回调,
//2. status的值只能为 RESOLVED 或者 REJECTED
//3. 这里需要使用箭头函数,不然函数调用时this会默认指向window
//4. 思路:当进入reject或者resolve回调时,Promise会改变到对应状态,同时将传入回调函数的参数保存,最后去执行回调队列里对应状态的函数
const handleCallBack = (status, data) => {
//promise的状态只能被改变一次,如果已经发生改变,那么直接返回
if (this.status !== PENDING) {
return
}
this.status = status === RESOLVED ? RESOLVED : REJECTED;//改变Promsie状态
this.data = data//保存回调参数
//回调函数是需要被异步执行的,这里使用了setTimeout
setTimeout(() => {
if (this.callBacks.length > 0) {
//遍历回调队列,根据Promise的状态去执行相对应成功或者失败的回调
this.callBacks.forEach(callBackObj => {
status === RESOLVED ? callBackObj.onResolved(data) : callBackObj.onRejected(data)
});
}
}, 0);
}
function resolve (value) {
handleCallBack(RESOLVED, value)
}
function reject (reason) {
handleCallBack(REJECTED, reason)
}
excutor(resolve, reject)
}
//实例方法
/**
*思路:
* 1. then 返回的是一个Promise对象
* 2. 如果当前Promise的状态是pending.那么将参数专递的回调函数,推入回调队列中,并在执行后改变Promise的状态
* 3. 如果当前状态不是pending,那么调用对应的回调函数,改变Promise状态并判断其返回结果类型,是Promise那么该Promise的状态和回调参数就是本次返回Promise的状态和参数,如果不是Promise那么直接进入成功回调,回调函数的参数就是本次回调函数的结果
* 4. 其中还需注意异常和未传回调函数这种情况的处理
* */
then (onResolved, onRejected) {
const that = this
onResolved = typeof onResolved === 'function' ? onResolved : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
return new Promise((resolve, reject) => {
function handle (callBack) {
try {
let result = undefined;
if (!callBack) {
result = that.status === RESOLVED ? onResolved(that.data) : onRejected(that.data)
} else {
result = callBack(that.data);
}
if (Promise.isPromise(result)) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
}
if (that.status === PENDING) {
that.callBacks.push({
onResolved: function () {
handle(onResolved);
},
onRejected: function () {
handle(onRejected);
}
})
} else {
setTimeout(() => {
handle();
}, 0);
}
})
}
catch (onRejected) {
return this.then(undefined, onRejected);
}
//静态方法
static resolve (value) {
return new Promise((resolve, reject) => {
if (Promise.isPromise(value)) {
value.then(resolve, reject);
} else {
resolve(value);
}
})
}
static reject (reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
static all (promises) {
if (!Array.isArray(promises)) {
throw Error(`Parameters ${promises} of Promise static method .all must be array`)
}
const results = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
results[index] = value
count++
if (count == promises.length) {
resolve(results)
}
},
reason => {
reject(reason)
}
)
})
})
}
static race (promises) {
if (!Array.isArray(promises)) {
throw Error(`Parameters ${promises} of Promise static method .all must be array`)
}
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
})
})
}
static isPromise (obj) {
return obj instanceof Promise
}
}
window.$P = window.mPromise = Promise
}(window))
结语
笔者能力有限,整理中可能有疏漏和错误,如有发现恳请指正交流,若有幸被阅有所帮助,留下小爪爪鼓励一下吧