手撕Promise

文章目录

    • 一、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接收两个参数,分别是resolvereject,这两个参数都是函数类型
我们可以用.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) {//判断响应状态码 2xxif (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

有三个值,分别是pendingfulfilledrejected
promise状态的改变只有两种,分别是:

1、pending => fulfilled
2、pending => rejected

而且一个promise对象只会改变一次,改变之后就不会再变

(2)结果值属性PromiseResult

这个属性保存的是异步成功或失败的结果
无论成功还是失败,都只有一个结果数据
这个结果是通过resolvereject这两个函数参数,保存在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,返回的是undefinedundefined也是非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 '出了问题';  //返回失败的Promisereturn 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=>resolvedthis.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=>resolvedthis.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方法返回的还是Promisereturn 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的状态如果要改变,需要调用resolvereject函数,而这两个函数的调用时机和回调息息相关。(感觉懂了,但是又没有完全懂)

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//调用回调函数 PromiseStateif (this.PromiseSate === 'fulfilled') {//获取回调函数的执行结果let result = onResolved(this.PromiseResult)//判断if (result instanceof Promise) {//传入的是Promise对象,则直接调用then方法,根据其自身的属性判断调用resolve还是rejectresult.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还是rejectresult.then(r => {resolve(r)}, e => {reject(e)});} else {//结果的对象状态为成功resolve(result)}} catch (error) {reject(error)}}const self = this//调用回调函数 PromiseStateif (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方法返回的还是Promisereturn 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方法返回的还是Promisereturn 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);  
执行顺序应是:111333222

在执行函数的地方包个定时器就行,但这里只是表现形式一样,真正的源码不是这样的,因为定时器是个红任务,源码应该是微任务……这里存疑

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方法返回的还是Promisereturn 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还是rejectresult.then(r => {resolve(r)}, e => {reject(e)});} else {//结果的对象状态为成功resolve(result)}} catch (error) {reject(error)}}const self = this//调用回调函数 PromiseStateif (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();

完结撒花!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/468857.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发系列教程(二十)--页面间动画

页面间动画 两个页面间发生跳转&#xff0c;一个页面消失&#xff0c;另一个页面出现&#xff0c;这时可以配置各自页面的页面转场参数实现自定义的页面转场效果 页面进入&#xff1a; PageTransitionEnter({type?: RouteType,duration?: number,curve?: Curve | string,…

【Android】使用Android Studio打包APK文件

文章目录 1. 新建项目2. 打包生成APK3. 安装APK 1. 新建项目 打包APK之前&#xff0c;首先需要新建项目&#xff0c;有基础的可以跳过。 无基础的可以参考&#xff1a;使用Android Studio运行Hello World项目 2. 打包生成APK 1.找到Build -> Generate Signed Bundle or …

C++-带你深度理解string类的常见接口

1. 为什么学习string类&#xff1f; C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需…

基于SSM的教材管理系统

文章目录 教材管理系统一、项目演示二、项目介绍三、系统部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;&#xff09; 教材管理系统 一、项目演示 基于SSM的教材管理系统 二、项目介绍 有三个角色 1、管理员 功能模块&#xff1a;用户管理、教…

Atcoder ABC339 A - TLD

TLD 时间限制&#xff1a;2s 内存限制&#xff1a;1024MB 【原题地址】 所有图片源自Atcoder&#xff0c;题目译文源自脚本Atcoder Better! 点击此处跳转至原题 【问题描述】 【输入格式】 【输出格式】 【样例1】 【样例输入1】 atcoder.jp【样例输出1】 jp【样例说明…

猫头虎分享已解决Bug ‍ || Python Error: KeyError: ‘key_name‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

linux信号机制[二]

阻塞信号 信号相关概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)。[收到信号但是没有处理]进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.注…

如何有效的向 AI 提问 ?

目录 〇、导言 一、Base LLM 与 Instruction Tuned LLM 二、如何提出有效的问题 &#xff1f; 1. 明确问题&#xff1a; 2. 简明扼要&#xff1a; 3. 避免二义性&#xff1a; 4. 避免绝对化的问题&#xff1a; 5. 利用引导词&#xff1a; 6. 检查语法和拼写&#xff1…

统计图饼图绘制方法(C语言)

统计图饼图绘制方法&#xff08;C语言&#xff09; 常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图。 前几类图比较容易绘制&#xff0c;饼图绘制较难。今值此介绍饼图的绘制方法。 本方法采用C语言的最基本功能&#xff1a; &#xff08; 1.&#xff09…

51单片机编程基础(C语言):LED点阵屏

点阵屏介绍 类似于数码管&#xff0c;要用到肉眼视觉效应。扫描&#xff0c;才能把每一个LED都能选中&#xff0c;从而显示我们想要的图形&#xff0c;否则&#xff0c; 只能一次点亮一个LED&#xff0c; LED使用 51单片机点阵屏电路图&#xff1a; 实际连接顺序如下图&#…

寒假作业:2024/2/14

作业1&#xff1a;编程实现二维数组的杨辉三角 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {int n;printf("please enter n:");scanf("%d",&n);int a…

17 ABCD数码管显示与动态扫描原理

1. 驱动八位数码管循环点亮 1.1 数码管结构图 数码管有两种结构&#xff0c;共阴极和共阳极&#xff0c;ACX720板上的是共阳极数码管&#xff0c;低电平点亮。 1.2 三位数码管等效电路图 为了节约I/O接口&#xff0c;各个数码管的各段发光管被连在一起&#xff0c;通过sel端…