文章目录
- 一、Promise的初体验
- 1.初体验——抽奖案例
- 二、Promise的实践练习
- 1.实践练习——fs读取文件
- 2.实践练习——AJAX请求
- 三、Promise的常见骚操作
- 1.封装fs读取文件操作
- 2.util.promisify方法进行promise风格转化
- 3.封装原生的Ajax
- 4.Promise实例对象的两个属性
- (1)状态属性`PromiseState`
- (2)结果值属性`PromiseResult`
- 5.Promise的工作流程
- 四、Promise中的API
- (1).then和.catch
- (2)Promise.resolve(参数)
- (3)Promise.reject(参数)
- (4)Promise.all([参数1,参数2…])
- (5)Promise.race([参数1,参数2…])
- 五、Promise的关键问题
- 1.改变对象状态的方式
- 2.能否执行多个回调
- 3.改变状态和指定回调的顺序
- 4.then/catch方法返回结果由什么决定
- 5.串联多个任务(链式调用)
- 6.异常穿透
- 7.中断Promise链条
- 六、手撕Promise
- 1.搭建整体结构
- 2.resolve和reject的实现
- 3.throw抛出错误改变状态
- 4.状态一旦改变就不能再变
- 5.异步任务回调的执行
- 6.执行多个then的回调
- 7.同步任务then方法的返回结果(难点)
- 8.异步任务then方法的返回结果(难点)
- 9.then方法中封装重复代码
- 10.catch方法封装,异常穿透(难点)
- 11.Promise.resolve方法封装
- 12.Promise.reject方法封装
- 13.Promise.all方法封装(难点)
- 14.Promise.race方法封装
- 15.细节:then方法的回调是异步执行的
- 16.class版本
- 四、异步编程终极解决方案
- 1.async函数
- 2.await关键字
- 3.发送ajax请求
Promise指定回调函数的方式更加灵活,且支持链式调用,可以解决回调地狱的问题。
回调地狱:回调函数嵌套调用,外部回调执行结果是内部函数执行的条件,不便于阅读且不便于异常处理,解决方式就是promise(或async/await)
一、Promise的初体验
1.初体验——抽奖案例
先来看一个案例:抽奖
<body>
<div>
<h2>点击按钮抽奖</h2>
<button id="btn">抽奖</button>
</div>
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
// 点击按钮,1s后显示是否中奖(30%概率中奖)
// 若中奖弹出恭喜恭喜,奖品为10万RMB劳斯莱斯优惠券
// 若未中奖弹出 再摆再厉
//获取元素对象
const btn = document.querySelector('#btn')
//注册事件
btn.addEventListener('click', function () {
//Promise 形式实现
//resolve解决函数类型的数据
//reject 拒绝函数类型的数据
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let n = rand(1, 100)
console.log(n);
if (n <= 30) {
resolve(n) //将 promise 对象的状态设置为 [成功]
} else {
reject(n) //将 promise 对象的状态设置为 [失败]
}
}, 1000)
})
//调用then方法
p.then((res) => {
console.log('异步成功:', res); //异步成功:12
}).catch((err) => {
console.log('异步失败:', err); //异步失败:66
});
})
</script>
</body>
Promise接收两个参数,分别是resolve
和reject
,这两个参数都是函数类型
我们可以用.then
来指定成功或失败的回调。(第一个回调是成功的回调,参数为调用resolve
时传过来的参数,第二个回调是失败的回调,参数为调用reject
传过来的参数)
p.then((res)=>{
console.log('异步成功:',res); //异步成功:12
},(error)=> {
console.log('异步失败:',error); //异步失败:66
})
下面这个用catch
写法和上面是等价的:
p.then((res)=>{
console.log('异步成功:',res); //异步成功:12
}).catch((error)=> {
console.log('异步失败:',error); //异步失败:66
})
二、Promise的实践练习
1.实践练习——fs读取文件
const fs = require("fs");
//回调函数形式
// fs.readFile('./resourse/context.txt', (err, data) => {
// //如果出错就抛出错误
// if (err) throw err;
// //反之读取文件
// console.log(data.toString());
// })
//Promise形式
const p = new Promise((resolve, reject) => {
fs.readFile('./resourse/context.txt', (err, data) => {
//如果出错
if (err) reject(err)
//如果成果
resolve(data)
})
})
p.then((result) => {
console.log(result.toString());
}).catch((err) => {
console.log(err);
});
2.实践练习——AJAX请求
<body>
<div class="container">
<h2>使用Promise封装原生AJAX</h2>
<button id="btn">点击发送AJAX请求</button>
</div>
<script>
//接口地址 https://api.apiopen.top/getJoke
//获取元素对象
const btn = document.querySelector('#btn')
//注册点击事件
btn.addEventListener('click', function () {
// //回调函数的写法
// //1. 创建对象
// const xhr = new XMLHttpRequest();
// //2.初始化
// xhr.open('GET', 'https://api.apiopen.top/getJoke')
// //3.发送
// xhr.send()
// //4.处理响应结果
// xhr.onreadystatechange = function () {
// if (xhr.readyState === 4) {
// //判断响应状态码 2xx
// if (xhr.status >= 200 && xhr.status < 300) {
// console.log(xhr.response);
// } else {
// console.log(xhr.status);
// }
// }
// }
//Promise的写法
const p = new Promise((resolve, reject) => {
//1. 创建对象
const xhr = new XMLHttpRequest();
//2.初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke')
//3.发送
xhr.send()
//4.处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断响应状态码 2xx
if (xhr.status >= 200 && xhr.status < 300) {
//控制台输出响应体
// console.log(xhr.response);
resolve(xhr.response)
} else {
//控制台输出响应状态码
// console.log(xhr.status);
reject(xhr.status)
}
}
}
})
p.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
});
})
</script>
</body>
三、Promise的常见骚操作
1.封装fs读取文件操作
/**
*封装一个函数 mineReadFile读取文件内容
* 参数:path 文件路径
*返回:promise对象
*/
function mineReadFile(path) {
return new Promise((resolve, reject) => {
require('fs').readFile(path, (err, data) => {
//判断
if (err) reject(err)
//成功
resolve(data)
})
})
}
mineReadFile('./resourse/context.txt')
.then((result) => {
console.log(result.toString());
}).catch((err) => {
console.log(err);
});
2.util.promisify方法进行promise风格转化
/*
util.promisify
*/
//1.引入utils
const util = require("util");
//2.引入fs
const fs = require("fs");
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile)
mineReadFile('./resourse/context.txt').then((result) => {
console.log(result.toString());
})
3.封装原生的Ajax
// 封装一个函数 sendAJAX 发送 GET AJAX 请求
// 参数 URL
// 返回结果Promise对象
function sendAJAX(url) {
return new Promise((resolve, reject) => {
//创建对象
const xhr = new XMLHttpRequest();
xhr.responseType = 'json'
//初始化
xhr.open('GET', url)
//发送
xhr.send()
//处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
//判断成功
if (xhr.status >= 200 && xhr.status < 300) {
//控制台输出响应体
resolve(xhr.response)
} else {
//控制台输出响应状态码
reject(xhr.status)
}
}
}
})
}
sendAJAX('https://ku.qingnian8.com/dataApi/news/navlist.php').then((result) => {
console.log(result);
}).catch((err) => {
console.warn(err);
});
4.Promise实例对象的两个属性
回到前边的抽奖案例,如果我们打印一下p,可以看到两个属性:
上面是成功,下面是失败
是的,这两个属性分别是状态属性PromiseState
和结果值属性PromiseResult
(1)状态属性PromiseState
有三个值,分别是pending
、fulfilled
、rejected
。
promise状态的改变只有两种,分别是:
1、pending => fulfilled
2、pending => rejected
而且一个promise对象只会改变一次,改变之后就不会再变
(2)结果值属性PromiseResult
这个属性保存的是异步成功或失败的结果
无论成功还是失败,都只有一个结果数据
这个结果是通过resolve
和reject
这两个函数参数,保存在PromiseResult
属性中的
成功穿过去的参数一般叫value,失败的参数一般叫reason(当然我习惯写res和err)
5.Promise的工作流程
四、Promise中的API
Promise对象中的函数是同步调用的,下面的代码依次输出:我同步执行,奥里给
const p = new Promise((resolve, reject) => {
//这个函数是同步调用的
console.log('我同步执行');
});
console.log('奥里给');
(1).then和.catch
这部分上面讲过了,可以去看看0.初体验
部分
(2)Promise.resolve(参数)
这个方法可以返回一个Promise对象,注意:
1、如果传入的参数是一个非Promise类型,那么返回一个成功的Promise对象,参数也就是成功的回调中的value值
const p1 = Promise.resolve(521);
console.log(p1);
2、如果传入的参数是一个Promise类型,那么返回的结果取决于传入的Promise的结果
如果结果为resolve
,那么返回成功的Promise,值就是里面调用resolve的参数成功
:
const p2 = Promise.resolve(new Promise((resolve,reject) => {
resolve('成功');
}))
console.log(p2);
3、如果结果为reject
,那么返回失败的Promise,值就是里面调用reject的参数失败
:
const p2 = Promise.resolve(new Promise((resolve,reject) => {
// resolve('成功');
reject('失败');
}))
console.log(p2);
不想让控制台报错,只需要用catch捕获一下:
const p2 = Promise.resolve(new Promise((resolve,reject) => {
// resolve('成功');
reject('失败');
}))
console.log(p2);
p2.catch(err => console.log(err));
(3)Promise.reject(参数)
不管传入的参数是什么,都会返回一个失败的Promise对象
,传入的参数是什么,结果就是什么(若参数是Promise对象,那么结果值
也是Promise对象)。
不管传入的参数是什么,都会返回一个失败的Promise对象
,传入的参数是什么,结果就是什么(若参数是Promise对象,那么结果值
也是Promise对象)。
1、如果传的是非Promise,返回的是失败的Promise,结果就是传的值:
const p3 = Promise.reject(520);
console.log(p3);
2、如果传的是成功的Promise,返回的是失败的Promise对象,该对象的结果值PromiseResult是成功的Promise对象:
const p4 = Promise.reject(new Promise((resolve,reject)=>{
resolve('OK');
// reject('错误');
}))
console.log(p4);
3、如果传的是失败的Promise,返回的是失败的Promise对象,该对象的结果值PromiseResult是失败的Promise对象:
const p4 = Promise.reject(new Promise((resolve,reject)=>{
// resolve('OK');
reject('错误');
}))
console.log(p4);
(4)Promise.all([参数1,参数2…])
1、返回结果是一个Promise对象,该方法参数是一个Promise对象构成的数组,只有所有的Promise成功,返回的状态才是成功,值为一个成功结果值构成的数组
const p1 = new Promise((resolve,reject)=> {
resolve('OK');
})
const p2 = Promise.resolve('成功');
const p3 = Promise.resolve('欧了');
const result = Promise.all([p1,p2,p3]);
console.log(result);
2、如果有一个失败,那么返回的Promise状态为失败,结果值为第一个失败的结果值
const p1 = new Promise((resolve,reject)=> {
resolve('OK');
})
const p2 = Promise.reject('失败');
const p3 = Promise.reject('完蛋');
const result = Promise.all([p1,p2,p3]);
console.log(result);
这玩意儿应用场景还是蛮多的,比如同时去改数据库的两个表,两个表都改完了,再使用.then
的回调提示用户修改成功。
(5)Promise.race([参数1,参数2…])
参数是多个Promise对象构成的数组,这个race本身是赛跑的意思,所以这个API的作用就是:多个异步操作,哪个先返回结果(先完成),那么调用这个API返回的就是谁。
比如下面这段代码,p1加了定时器,那么p2先返回结果,所以result返回的就是一个失败的Promise,值是p2的结果值
const p1 = new Promise((resolve,reject)=> {
setTimeout(() => {
resolve('OK');
}, 1000);
})
const p2 = Promise.reject('失败');
const p3 = Promise.reject('完蛋');
const result = Promise.race([p1,p2,p3]);
console.log(result);
五、Promise的关键问题
1.改变对象状态的方式
有三种方式可以改变Promise对象的状态,分别是1.resolve函数 2.reject函数 3.抛出错误
const p = new Promise((resolve,reject)=> {
//1.resolve函数
resolve('成功'); //pending => resolved/fulfilled
//2.reject函数
reject('失败'); //pending => rejected
//3.抛出错误
throw '出问题了!'; //pending => rejected
})
console.log(p);
2.能否执行多个回调
只要Promise对象的状态改变,那么对应的回调不管有几个,都会执行
const p = new Promise((resolve,reject)=> {
resolve('DJ');
})
//只要p状态改变为resolved,下面三个回调都会执行
p.then(res=>console.log(res));
p.then(res=>alert(res));
p.then(res=>console.log(res,'drop the beat'));
3.改变状态和指定回调的顺序
有两种情况:
1、若状态改变为同步,那么就是改变状态 => 指定回调 => 执行回调
const p = new Promise((resolve,reject)=> {
resolve('DJ');
})
p.then(res=>console.log(res));
2、若状态改变为异步,那么就是指定回调 => 改变状态 => 执行回调
const p = new Promise((resolve,reject)=> {
setTimeout(()=>{
resolve('DJ');
},1000)
})
p.then(res=>console.log(res));
记住,执行回调永远在状态改变之后,执行回调和指定回调不是一个概念。指定回调是执行then方法而不是执行then里的嘎达们
4.then/catch方法返回结果由什么决定
调用then(或catch)方法返回的还是Promise对象,返回的这个Promise的状态和结果取决于then中回调的返回值。
1、如果返回非Promise,那么结果为成功的Promise对象,值就是返回值。(这里如果不写返回值,返回的也是成功的Promise,因为不写return,返回的是
undefined
,undefined
也是非Promise类型的数据)
2、抛出错误,那么结果为失败的Promise对象,值就是抛出的值
3、返回一个Promise对象,那么结果取决于该Promise的状态
不管p的状态是成功还是失败,后面链式调用then(或catch)返回的结果,都取决于它里面的回调,就是上面那三个情况
const p = new Promise((resolve,reject)=> {
resolve('成功');
})
const result = p.then(res => {
//1.返回非Promise,那么结果为成功的Promise对象,值就是返回值
//(不写return返回undefined)
return 521; //result结果为fulfilled、521
//2.抛出错误,那么结果为失败的Promise对象,值就是抛出的值
throw '出了问题'; //result结果为rejected、出了问题
//3.返回一个Promise,那么结果取决于该Promise的状态和值
return new Promise((resolve,reject)=> {
// resolve('OK');
reject('ERROR');
})
}, error => {
// return 521; //返回成功的Promise
// throw '出了问题'; //返回失败的Promise
return new Promise((resolve,reject)=> {
resolve('OK'); //返回成功的Promise,值为OK
// reject('ERROR');
})
})
console.log(result); //上面代码没注释,这里的结果应根据返回值决定
catch也是一样的规则。
const p = new Promise((resolve,reject)=> {
resolve('成功');
})
const result = p.catch(err => {
//return 521; //返回成功的Promise,值为521
//throw '出了问题'; //result结果为rejected、出了问题
return new Promise((resolve,reject)=> {
// resolve('OK');
reject('ERROR');
})
})
console.log(result); //返回失败的Promise,值为ERROR
5.串联多个任务(链式调用)
由于.then/.catch
返回的还是Promise,所以可以链式调用,解决回调地狱问题。下一个.then/.catch
中的回调的参数res是上一个then的Promise结果值(如果上一个没写返回值,那么上一个返回成功的Promise,值为undefined
)
const p = new Promise((resolve, reject) => {
resolve('ok');
})
p.then(res => {
return new Promise((resolve, reject) => {
resolve('success');
})
}).then(res => {
console.log(res); //success
}).then(res => {
console.log(res); //undefined
})
6.异常穿透
链式调用,在最后写个.catch
(或者.then
第一个回调写空,第二个回调捕获错误)可以捕获到前面的第一个错误,如果.then
中途出现错误,则依次向下执行,寻找处理错误的回调,直到找到.catch
(或某个.then
的第二个回调),然后执行该回调。
const p = new Promise((resolve,reject) => {
resolve('ok');
// reject('错误!');
})
p.then(res => {
console.log('111');
}).then(res => {
console.log('222');
throw '嗷嗷嗷错误'
}).then(res => {
console.log('333');
}).catch(err => {
console.warn(err); //嗷嗷嗷错误
})
7.中断Promise链条
其实我们在.then
时,不管上一个Promise结果是什么,所有.then
都会依次执行,因为每个.then
都有两个回调(第二个回调是捕获错误的回调),比如下面的代码,输出结果是:111,dj,333
,第二个.then
执行了捕获错误的回调,由于没有返回值,返回一个成功的Promise,值为undefined
,所以后面的.then
还会继续调用
const p = new Promise((resolve, reject) => {
resolve('ok');
})
p.then(res => {
console.log('111');
throw 'dj';
}).then(res => console.log('222'), err => console.log(err))
.then(res => console.log('333'), err => console.log(err))
想要中断.then
和最后.catch
的调用,有且只有一种方式:
返回一个
pending
状态的Promise
const p = new Promise((resolve, reject) => {
resolve('ok');
})
p.then(res => {
console.log('111');
// throw 'dj';
return new Promise((resolve,reject)=>{}); //pending
}).then(res => console.log('222'), err => console.log(err))
.then(res => console.log('333'), err => console.log(err))
上面程序执行结果为:111
六、手撕Promise
1.搭建整体结构
先正常搭一个Promise的使用,只不过这个Promise是从我们自定义的文件中引入的构造函数(或者类,这里用ES5的构造函数写吧)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手撕Promise</title>
<script src="./Promise.js"></script>
</head>
<body>
<script>
const p = new Promise((resolve,reject)=>{
resolve('ok');
})
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})
</script>
</body>
</html>
promise.js
function Promise(executer) {
}
//1.then方法的封装
Promise.prototype.then = function(onResolved,onRejected) {
}
2.resolve和reject的实现
这里的参数executer是一个函数,且是立即执行的,executer的参数是两个函数,分别是成功和失败的函数,即我们常写的resolve和reject。这两个函数调用做的事情一样:1、改变对象的状态,2、改变对象的结果值
//声明构造函数
function Promise(executor) {
//添加属性
this.PromiseSate = 'pending'
this.PromiseResult = null
//保存实例对象的this值
const self = this
//resolve函数
function resolve(data) {
//1.修改对象的状态(promisestate)
self.PromiseSate = 'fulfilled' //resolved
//2.修改对象的结果(promiseresult)
self.PromiseResult = data
}
//reject函数
function reject(data) {
//1.修改对象的状态(promisestate)
self.PromiseSate = 'rejected'
//2.修改对象的结果(promiseresult)
self.PromiseResult = data
}
//同步调用[执行器函数]
executor(resolve, reject)
}
//添加then方法
Promise.prototype.then(onResolved, onRejected){
}
3.throw抛出错误改变状态
如果我们要抛出错误:
const p = new Promise((resolve,reject)=>{
throw 'error';
})
123
那么应该在Promise构造函数的立即执行函数executer外层包一个try...catch
try {
executer(success, fail);
} catch(data) {
//如果抛出错误,那么就执行下面的代码
fail(data);
}
123456
这样就可以捕获异常,并改变Promise对象的状态
4.状态一旦改变就不能再变
只需要在成功和失败的回调中加个判断,如果当前状态不是pending,说明状态已经改变了,就不再执行后面的代码。
if(this.PromiseState !== 'pending') return;
5.异步任务回调的执行
如果是异步任务,那么根据js代码同步执行特性,会先调用then方法,再改变状态,我们要做到在改变状态后再执行then方法中的回调,怎么办?
const p = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok');
}, 1000);
})
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})
如果先调用then方法,那么Promise的状态的pending,所以要加个判断,当状态为pending时,就把两个回调保存到Promise实例的某个属性上
function Promise(executer) {
this.PromiseState = 'pending';//默认应该是等待
this.PromiseResult = undefined;
//定义一个属性来存放then函数的回调
this.callback = {};
......
}
Promise.prototype.then = function (onResolved, onRejected) {
//判断同步任务下走哪个回调
if (this.PromiseState === 'resolved') {
onResolved(this.PromiseResult);
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
//如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseState === 'pending') {
//把回调存到该实例的属性上
this.callback.onResolved = onResolved;
this.callback.onRejected = onRejected;
}
}
这样我们就可以实现,异步任务resolve/reject
函数调用导致状态改变后,再执行对应的回调
function Promise(executer) {
......
const success = (data) => {
//这是resolve对应的回调
//状态改变后就不能再变,加个判断
if (this.PromiseState !== 'pending') return;
//1.改变对象的状态:pending=>resolved
this.PromiseState = 'resolved';
//2.改变对象的结果值
this.PromiseResult = data;
//3.如果是异步,要在以上步骤结束后,执行对应的回调
if(this.callback.onResolved){
this.callback.onResolved(data);
}
}
const fail = (data) => {
//这是reject对应的回调
//状态改变后就不能再变,加个判断
if (this.PromiseState !== 'pending') return;
//1.改变对象的状态:pending=>resolved
this.PromiseState = 'rejected';
//2.改变对象的结果值
this.PromiseResult = data;
//3.如果是异步,要在以上步骤结束后,执行对应的回调
if(this.callback.onRejected) {
this.callback.onRejected(data);
}
}
......
}
6.执行多个then的回调
异步任务Promise如果有多个then的回调,那么原Promise中是全部都要执行的,但是我们自己封装的这个,会执行最后一个,原因在于我们调用then方法是往对象中保存回调,这样的话后面then方法的回调会覆盖前面的回调。
const p = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok');
}, 1000);
// reject('notok');
// throw 'error';
})
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})
p.then(res=>{
alert(res);
}, err => {
alert(err);
})
所以这时我们应该修改一下数据结构,把保存到实例上的属性变成数组,每次调用then方法,就把成功和失败的回调以对象的形式push进去
function Promise(executer) {
......
//定义一个属性来存放then函数的回调们
this.callbacks = [];
}
Promise.prototype.then = function (onResolved, onRejected) {
......
//如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseState === 'pending') {
//把回调存到该实例的属性上
// this.callback.onResolved = onResolved;
// this.callback.onRejected = onRejected;
this.callbacks.push({
onResolved: onResolved,
onRejected //简写
})
}
}
然后再遍历调用
function Promise(executer) {
......
const success = (data) => {
//这是resolve对应的回调
......
//3.如果是异步,要在以上步骤结束后,执行对应的回调
// if(this.callback.onResolved){
// this.callback.onResolved(data);
// }
if(this.callbacks.length != 0) {
this.callbacks.forEach(item => {
item.onResolved(data);
})
}
}
const fail = (data) => {
//这是reject对应的回调
......
//3.如果是异步,要在以上步骤结束后,执行对应的回调
// if(this.callback.onRejected) {
// this.callback.onRejected(data);
// }
if(this.callbacks.length != 0) {
this.callbacks.forEach(item => {
item.onRejected(data);
})
}
}
......
}
7.同步任务then方法的返回结果(难点)
我们知道,执行then方法,返回的还是Promise,且返回Promise的状态是then中回调决定的,具体去看第二章第四节。所以,我们应该对then方法来一些小小的改写
首先包个返回Promise,Promise的回调是同步执行的
Promise.prototype.then = function (onResolved, onRejected) {
//执行then方法返回的还是Promise
return new Promise((resolve, reject) => {
//判断同步任务下走哪个回调
if (this.PromiseState === 'resolved') {
......
}
if (this.PromiseState === 'rejected') {
.......
}
//如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseState === 'pending') {
......
}
})
}
既然then的返回Promise状态和结果是由回调决定的,那我们就要拿到回调的返回值,然后判断返回值是否是Promise类型的数据。
1、非Promise那么就返回成功的Promise,结果值就是回调的返回值。
2、如果是Promise类型的话,那么一定有then方法,成功会走第一个,失败会走第二个。
其实总体来说,要抓住一个要点,就是调用then
方法返回的Promise的状态如果要改变,需要调用resolve
和reject
函数,而这两个函数的调用时机和回调息息相关。(感觉懂了,但是又没有完全懂)
8.异步任务then方法的返回结果(难点)
这里仍然是保存回调到Promise对象的属性上,不同的是我们不只是保存成功和失败的回调,而是保存更多东西,那就是对返回的Promise的状态的改变操作resolve/reject
。
1、套个箭头函数保存到属性上,当异步结束后会在状态改变后执行这个箭头函数(也可以在外侧自定义一个值保存this)
2、首先要执行then中的回调,并拿到返回值,进而判断是否是Promise类型的数据
3、非Promise就调用resolve,是Promise再…和上面同步是一样的操作
4、不同的是,在异步中需要加try-catch
,这是因为这个函数的执行不在Promise的主函数中(在改变状态的函数中),所以异常无法捕获,需要我们手动捕获
//添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
const self = this
//调用回调函数 PromiseState
if (this.PromiseSate === 'fulfilled') {
//获取回调函数的执行结果
let result = onResolved(this.PromiseResult)
//判断
if (result instanceof Promise) {
//传入的是Promise对象,则直接调用then方法,根据其自身的属性判断调用resolve还是reject
result.then(r => {
resolve(r)
}, e => {
reject(e)
});
} else {
//结果的对象状态为成功
resolve(result)
}
}
if (this.PromiseSate === 'rejected') {
onRejected(this.PromiseResult)
}
//判断 pending 状态,如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseSate === 'pending') {
//把回调存到该实例的属性上
this.callbacks.push({
onResolved: function () {
try {
//执行成功的回调
let result = onResolved(self.PromiseResult)
//判断执行结果
if (result instanceof Promise) {
result.then(r => {
resolve(r)
}, e => {
reject(e)
});
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
},
onRejected: function () {
try {
//执行失败的回调
let result = onRejected(self.PromiseResult)
//判断执行结果
if (result instanceof Promise) {
result.then(r => {
resolve(r)
}, e => {
reject(e)
});
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
},
})
}
})
}
9.then方法中封装重复代码
//添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
//封装函数
function callback(type) {
try {
//获取回调函数的执行结果
let result = type(self.PromiseResult)
//判断
if (result instanceof Promise) {
//传入的是Promise对象,则直接调用then方法,根据其自身的属性判断调用resolve还是reject
result.then(r => {
resolve(r)
}, e => {
reject(e)
});
} else {
//结果的对象状态为成功
resolve(result)
}
} catch (error) {
reject(error)
}
}
const self = this
//调用回调函数 PromiseState
if (this.PromiseSate === 'fulfilled') {
callback(onResolved)
}
if (this.PromiseSate === 'rejected') {
callback(onRejected)
}
//判断 pending 状态,如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseSate === 'pending') {
//把回调存到该实例的属性上
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
},
})
}
})
}
10.catch方法封装,异常穿透(难点)
const p = new Promise((resolve,reject)=>{
reject('ok');
})
p.then(res => {
console.log(111);
}).then(res => {
console.log(222);
}).then(res => {
console.log(333);
}).catch(err => {
console.log(err)
})
catch方法逻辑和then差不多,所以可以直接调用then,返回Promise
对象。不同的是catch只接收错误的回调,所以成功的回调这里传个undefined
Promise.prototype.catch = function(onRejected) {
return this.then(undefined, onRejected);
}
但是只有这样的话,上面的链式调用会报错,因为then
中都没有指定第二个错误回调,所以第二个回调是undefined
,如果状态是reject
,那么怎么去调一个undefined
?所以我们要在then方法中指定一个错误回调的默认值,这个默认值要抛出错误,从而继续往后面查找错误的回调,直到找到catch
方法,这样就实现了异常穿透
Promise.prototype.then = function (onResolved, onRejected) {
//判断第二个回调是否存在,没有就写个默认的
if(typeof onRejected !== 'function') {
onRejected = err => {
console.log('第二个默认回调执行');
throw err;//一直往后找错误的回调
}
}
//执行then方法返回的还是Promise
return new Promise((resolve, reject) => {
......
})
}
同样的,如果不写第一个回调,我们也要实现值的传递
Promise.prototype.then = function (onResolved, onRejected) {
//判断第二个回调是否存在,没有就写个默认的
if(typeof onRejected !== 'function') {
onRejected = err => {
console.log('第二个默认回调执行');
throw err;//一直往后找错误的回调
}
}
//如果不写第一个回调,也能实现值的传递
if(typeof onResolved !== 'function') {
onResolved = res => {
console.log('第一个默认回调执行');
return res
};
}
//执行then方法返回的还是Promise
return new Promise((resolve, reject) => {
......
})
}
11.Promise.resolve方法封装
由于这个方法是在Promise上的方法,不是实例上的,所以不用写在原型上
const p = Promise.resolve(521);
console.log(p)
const p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('ok');
}))
console.log(p2)
//3.resolve方法封装
Promise.resolve = function(data) {
return new Promise((resolve, reject) => {
if(data instanceof Promise) {
data.then(res => {
resolve(res);
}, err => {
reject(err);
})
}else {
resolve(data);
}
})
}
12.Promise.reject方法封装
不管参数是什么,都返回失败的Promise。这个比较简单,就不多说了
//4.reject方法封装
Promise.reject = function(data) {
return new Promise((resolve, reject) => {
reject(data);
})
}
13.Promise.all方法封装(难点)
//测试all方法
const p1 = new Promise((resolve, reject) => {
resolve('hello')
})
const p2 = Promise.resolve('ok')
const p3 = Promise.resolve('bad')
let result = Promise.all([p1, p2, p3])
console.log(result);
all方法的原则就是,传入一个由Promise组成的数组,返回一个Promise,只有全部Promise都成功,才返回成功的Promise,值为成功结果值组成的数组,只要有一个失败就返回失败的Promise,值为失败的结果值。
所以,我们可以使用for循环去检索每一个Promise对象,既然是Promise,就一定有then方法,成功走第一个回调,失败走第二个回调。
1、设置一个count,用来记录每有一个promise对象是成功的,其自增1
2、设置一个数组arr,用来存储成功的结果值
3、for循环结束后进行判断,如果count的值和传入数组的长度相同,说明所有的Promise都是成功的。
4、注意,状态改变后就不能再变噢
//添加all方法
Promise.all = function (arrPromise) {
return new Promise((resolve, reject) => {
//声明变量
let count = 0; //记录正确的数量
let arr = [] //若成功返回的结果数组
//遍历
for (let i = 0; i < arrPromise.length; i++) {
arrPromise[i].then((result) => {
//得知对象的状态是成功
count++
//把当前promis e对象的结果存入数组
// arr.push(res); //这么写可能会由于异步出现顺序问题
arr[i] = result //这么写没事,是let的块级作用域效果
//每个promise对象都成功
if (count == arrPromise.length) {
resolve(arr)
}
}).catch((err) => {
reject(err)
});
}
})
}
这里有一个尤其要注意的点,就是保存成功结果值到arr
时,要使用arr[i] = res;
,不使用arr.push(res);
,因为如果使用push,当传入的Promise中有异步任务时,调用回调的时机会不同,这样的话push的顺序可能就会发生变化,和传入的Promise顺序不同。
而使用arr[i] = res;
,正好借助了let块级作用域的特点,解决了这个问题。这是借助了下标i
去规定它们的顺序,let声明的循环每一轮都会保存相应的i值,不管谁先执行完,顺序都是跟i
保持一致的。
比如下面这个例子,如果是push,那么后面两个会先push;如果是arr[i] =res
,也是后面两个先添加,第一个最后添加,但是顺序不变。
14.Promise.race方法封装
这个就比较简单了,哪个先改变状态,哪个就决定返回的结果和状态。
//添加race方法
Promise.race = function (arrPromise) {
return new Promise((resolve, reject) => {
for (let i = 0; i < arrPromise.length; i++) {
arrPromise[i].then((result) => {
//修改返回对象的结果为 [成功]
resolve(result)
}).catch((err) => {
//修改返回对象的结果为 [失败]
reject(err)
});
}
})
}
15.细节:then方法的回调是异步执行的
const p = new Promise((resolve, reject) => {
resolve('ok');
console.log(111);
})
p.then(res => {
console.log(222);
})
console.log(333);
执行顺序应是:111,333,222
在执行函数的地方包个定时器就行,但这里只是表现形式一样,真正的源码不是这样的,因为定时器是个红任务,源码应该是微任务……这里存疑
function Promise(executer) {
......
const success = (data) => {
......
if (this.callbacks.length != 0) {
setTimeout(() => {
this.callbacks.forEach(item => {
item.onResolved();
})
});
}
}
const fail = (data) => {
......
if (this.callbacks.length != 0) {
setTimeout(() => {
this.callbacks.forEach(item => {
item.onRejected();
})
});
}
}
......
}
//1.then方法的封装
Promise.prototype.then = function (onResolved, onRejected) {
......
//执行then方法返回的还是Promise
return new Promise((resolve, reject) => {
//公共的改变返回Promise状态的方法封装起来
......
//判断同步任务下走哪个回调
if (this.PromiseState === 'resolved') {
setTimeout(() => {
changeState(onResolved);
});
//如果抛出错误,不用再另外写try-catch,封装时写过了
//所以任何Promise实例执行器函数出现错误,都可以直接捕获
}
if (this.PromiseState === 'rejected') {
setTimeout(() => {
changeState(onRejected);
});
}
......
})
}
......
16.class版本
class Promise {
//构造方法
constructor(executor) {
//添加属性
this.PromiseSate = 'pending'
this.PromiseResult = null
//保存实例对象的this值
const self = this
//定义一个属性来存放then函数的回调
this.callbacks = [];
//resolve函数
function resolve(data) {
//判断状态是否改变过,改变过则不继续改动,否则修改状态
if (self.PromiseSate !== 'pending') return;
//1.修改对象的状态(promisestate)
self.PromiseSate = 'fulfilled' //resolved
//2.修改对象的结果(promiseresult)
self.PromiseResult = data
//调用成功的回调函数
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
},);
}
//reject函数
function reject(data) {
//判断状态是否改变过,改变过则不继续改动,否则修改状态
if (self.PromiseSate !== 'pending') return;
//1.修改对象的状态(promisestate)
self.PromiseSate = 'rejected'
//2.修改对象的结果(promiseresult)
self.PromiseResult = data
//调用失败的回调函数
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
});
}
try {
//同步调用[执行器函数]
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
//then方法封装
then(onResolved, onRejected) {
//判断第二个回调是否存在,没有就写个默认的
if (typeof onRejected !== 'function') {
onRejected = err => {
// console.warn(err);
throw err
}
}
//如果不写第一个回调,也能实现值的传递
if (typeof onResolved !== 'function') {
onResolved = res => {
// console.log('第一个默认回调执行');
return res
};
}
return new Promise((resolve, reject) => {
//封装函数
function callback(type) {
try {
//获取回调函数的执行结果
let result = type(self.PromiseResult)
//判断
if (result instanceof Promise) {
//传入的是Promise对象,则直接调用then方法,根据其自身的属性判断调用resolve还是reject
result.then(r => {
resolve(r)
}, e => {
reject(e)
});
} else {
//结果的对象状态为成功
resolve(result)
}
} catch (error) {
reject(error)
}
}
const self = this
//调用回调函数 PromiseState
if (this.PromiseSate === 'fulfilled') {
setTimeout(() => {
callback(onResolved)
},);
}
if (this.PromiseSate === 'rejected') {
setTimeout(() => {
callback(onRejected)
},);
}
//判断 pending 状态,如果是异步任务(先指定回调再改变状态再执行回调)
if (this.PromiseSate === 'pending') {
//把回调存到该实例的属性上
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
},
})
}
})
}
//catch方法封装
catch(onRejected) {
return this.then(undefined, onRejected)
}
//resolve方法封装
static resolve(val) {
return new Promise((resolve, reject) => {
if (val instanceof Promise) {
val.then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
});
} else {
resolve(val)
}
})
}
//all方法封装
static all(arrPromise) {
return new Promise((resolve, reject) => {
//声明变量
let count = 0; //记录正确的数量
let arr = [] //若成功返回的结果数组
//遍历
for (let i = 0; i < arrPromise.length; i++) {
arrPromise[i].then((result) => {
//得知对象的状态是成功
count++
//把当前promis e对象的结果存入数组
// arr.push(res); //这么写可能会由于异步出现顺序问题
arr[i] = result //这么写没事,是let的块级作用域效果
//每个promise对象都成功
if (count == arrPromise.length) {
resolve(arr)
}
}).catch((err) => {
reject(err)
});
}
})
}
//race方法封装
static race(arrPromise) {
return new Promise((resolve, reject) => {
for (let i = 0; i < arrPromise.length; i++) {
arrPromise[i].then((result) => {
//修改返回对象的结果为 [成功]
resolve(result)
}).catch((err) => {
//修改返回对象的结果为 [失败]
reject(err)
});
}
})
}
}
四、异步编程终极解决方案
1.async函数
调用async函数返回一个Promise,该Promise的状态和值由async函数的返回值来决定
1、非Promise则返回一个成功的Promise,值为返回值
2、Promise则返回结果由它来决定
3、抛出错误则返回错误的Promise,值为抛出的值
async function main() {
//1.非Promise则返回一个成功的Promise,值为返回值
// return 521;
//2.Promise则返回结果由它来决定
return new Promise((resolve, reject) => {
reject('error');
})
//3.抛出错误则返回错误的Promise,值为抛出的值
// throw '错误';
}
console.log(main());
2.await关键字
1、await后边一般跟的是Promise对象,也可以是其他值
2、如果跟的是Promise对象,await返回的是Promise成功的值
3、如果跟的是其他值,直接将此值作为返回值
4、如果await后面跟的是失败的Promise,会抛出异常,需要try-catch捕获
5、await只能写在async中,但async中可以没有await
async function main(){
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1.右侧为promise的情况
// let res = await p;
//2.右侧为其他类型的数据
//let res2 = await 20;
//3.如果promise是失败的状态
try{
let res3=await p;
}catch(e){
console.log(e);
}
}
main();
3.发送ajax请求
function sendAjax() {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json'; //指定返回结果格式
xhr.open('GET', 'https://ku.qingnian8.com/dataApi/news/navlist.php');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
}
async function getNews() {
try{
let result = await sendAjax();
console.log(result);
}catch(e) {
console.log(e);
}
}
getNews();
完结撒花!