Promise理论及手写
Promise理论及手写
预备知识
错误的类型
Error:所有错误的父类型
ReferenceError:引用的变量不存在
TypeError:数据类型不正确
let b
console.log(b.xxx)
// TypeError:Cannot read property 'xxx' of undefined
let c = {}
c.xxx()
// TypeError:c.xxx is not a function
RangeError:数据值不在其所允许的范围内
SyntaxError:语法错误
错误处理
抛出错误:throw error
捕获错误:try … catch
错误对象:
massage属性:错误相关信息
stack属性:函数调用栈记录信息
try {
let d
console.log(d.xxx)
} catch (error) {
console.log(error.message)
console.log(error.stack)
}
console.log('出错之后')
// Cannot read property 'xxx' of undefined
// TypeError:Cannot read property 'xxx' of undefined
// 出错之后
这里因为错误被捕获处理了,后面的代码才能够运行下去,打印出 出错之后
promise的理解和使用
promise是ES6规范中的一门新的技术,是js中进行异步编程的新的解决方案
promise支持链式调用,可以解决回调地狱,并且它指定回调的方式更加地灵活。
异步编程包括:
1、fs文件操作
// 采用了node中的fs模块 所以不能在浏览器中运行,只能写js脚本放到node中运行
// 使用promise进行读取
const p = new Promise((resolve,reject) => {
const fs = require('fs');
fs.readFile("./content.tx",(err,data) => {
if(err) reject(err);
else resolve(data.toString());
});
})
p.then(value => {
console.log(value);
},reason => {
console.log('now in promise')
console.log(reason);
})
2、数据库操作
3、Ajax
// 使用了ajax,node中没有 所以可以在浏览器中运行 直接写在html文件中
<script>
let btn = document.querySelector('button');
btn.addEventListener('click', function() {
const p = new Promise((resolve,reject) => {
// 开始请求过程
// 1、创建对象
const xhr = new XMLHttpRequest();
// 2、初始化
xhr.open('GET','https://zyydgrbk.top');
// 3、发送
xhr.send();
// 4、接收
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
p.then(value => {
console.log(value);
}, reason => {
console.log('now in promise')
console.log(reason);
})
})
</script>
4、定时器
// 写了一个中奖的定时器 写在promise中
<!-- 改写成promise -->
<script>
let btn = document.addEventListener('click', IsZhong);
function IsZhong() {
let n = Math.round(Math.random() * 100);
const p = new Promise((resolve,reject) => {
setTimeout(() => {
if(n<=30) {
resolve(n);
} else {
reject(n);
}
}, 1000);
});
p.then(value => {
console.log(value);
console.log('恭喜你,中奖了!');
}, reason => {
console.log(reason);
console.log('很遗憾,未中奖!');
})
}
</script>
其它的知识点可以通过以下来进行补充,自己就不打出来了
(96条消息) 【Promise】入门-同步回调-异步回调-JS中的异常error处理-Promis的理解和使用-基本使用-链式调用-七个关键问题_YK菌的博客-CSDN博客
promise手写
整体框架
定义整体框架,以及能够实现基本的resolve和reject方法
function Promise(executor) {
const self = this;
// 这里下面也需要写成self的形式,因为不然之后this的指向会出问题,反正this这里需要再好好看一下
self.promiseState = 'pending';
self.promiseResult;
// 在resolve和reject中主要做的就是转换promise状态 以及获取到值
function resolve(value) {
// 因为resolve是直接调用,不是通过对象调用的,所以注意this指向
self.promiseState = 'fulfilled';
self.promiseResult = value;
}
function reject(value) {
self.promiseState = 'rejected';
self.promiseResult = value;
}
executor(resolve,reject);
}
Promise.prototype.then = function(onResolved,onRejected) {
// 这里需要根据promise对象的状态决定执行哪个方法
// 如果是成功的 执行onResolved
// 说明一下为什么这里可以使用this来进行获取
// 因为这里的then是通过对象调用的 this指向就是promise实例 它本身有promiseState这个属性
if(this.promiseState === 'fulfilled') {
onResolved(this.promiseResult);
}
// 否则执行onRejected
if(this.promiseState === 'rejected') {
onRejected(this.promiseResult);
}
}
抛出错误
接下来是捕获到错误 改变状态不止通过resolve和reject这两种方法
// 在Promise构造函数中添加
// 对于抛出异常的情况,需要在executor中进行捕获
try{
executor(resolve,reject);
}
catch(e) {
reject(e)
}
// 控制台打印出p 可以看到状态已经换成了rejected
Promise {promiseState: 'rejected', promiseResult: 'error'}
promiseResult:"error"
promiseState:"rejected"
[[Prototype]]:Object
修改一次状态
完成状态只能够修改一次,就是如果状态已经修改了,则不能够进行修改了,否则可以修改一次
function resolve(value) {
// 如果只能够修改一次的话,需要判断此时的状态是否是pending 是pending才能够修改,否则不能够修改
if(self.promiseState !== 'pending') return;
// 因为resolve是直接调用,不是通过对象调用的,所以注意this指向
self.promiseState = 'fulfilled';
self.promiseResult = value;
}
异步then实现
异步任务then的实现 当promise中是异步任务时,then也需要在状态改变之后才能执行指定的回调
// 需要在then方法中添加
// 如果promise中是异步的情况,then执行时状态还是在pending时
// 所以将指定回调保存下来,在状态改变时进行调用即可
// 保存到外部是非常不安全的,保存到自身比较好
if(this.promiseState === 'pending') {
this.callback.push({
onResolved,
onRejected
});
}
// 在Promise构造函数中添加
self.callback = [];
// 在resolve和reject函数中对应添加
// 执行resolve时肯定状态是确定转变为成功了
// 那么如果这时有callback回调函数 则需要执行
if(self.callback.onRejected) {
self.callback.forEach(item => {
item.onRejected(value);
})
}
同步then返回结果
then的返回结果由里面的onResolve或onReject回调函数的执行结果决定
规则:
1、如果onResolve或onReject回调函数的执行结果返回非promise类型的数据
then的返回结果为成功的promise
2、如果onResolve或onReject回调函数的执行结果返回promise类型的数据
then的返回结果就是onResolve或onReject回调函数的执行结果
// then方法里面添加
const self = this;
if(self.promiseState === 'fulfilled') {
// 对抛出异常的情况需要在resolve中进行捕获
try{
// 这里需要获取到成功的回调函数的结果
// 观察它是否是promise
let res = onResolved(self.promiseResult);
if(res instanceof Promise) {
// 如果是promise对象 那它一定有自己的then方法,可以通过他自己的resolve或者reject来得到返回promise的状态改变
res.then(v => {resolve(v)}, r => {reject(r)});
} else {
// 如果是非promise 将该返回的promise对象的状态转变为成功即可,且值为回调函数返回的值
resolve(res);
}
} catch(e) {
reject(e);
}
}
异步then返回结果
if(this.promiseState === 'pending') {
this.callback.push({
// 之前是直接push原来的onResolve即可,但是现在是异步的,需要对原来的函数改变一下
onResolved: function () {
try{
let res = onResolved(self.promiseResult);
if(res instanceof Promise) {
res.then(
v => resolve(v),
r => reject(r)
);
} else {
resolve(res);
}
} catch(e) {
reject(e);
}
},
then优化与完善
可以看到之前的代码中有很多的try catch,其中它们内部的函数都差不多,只不过有个onResolved或者onRejected的区别,所以可以对该代码进行封装即可
// then方法的优化与完善
function callback(type) {
try{
// 这里需要获取到成功的回调函数的结果
// 观察它是否是promise
let res = type(self.promiseResult);
if(res instanceof Promise) {
// 如果是promise对象 那它一定有自己的then方法,可以通过他自己的resolve或者reject来得到返回promise的状态改变
res.then(v => {resolve(v)}, r => {reject(r)});
} else {
// 如果是非promise 将该返回的promise对象的状态转变为成功即可,且值为回调函数返回的值
resolve(res);
}
} catch(e) {
reject(e);
}
}
// 在下方使用的时候就是这样的使用方法
if(self.promiseState === 'fulfilled') {
callback(onResolved);
}
catch方法与异常穿透
catch其实是js中的语法糖,依旧使用的是then的代码,只不过resolve传入undefined即可
异常穿透:在then的链式调用中,中间任务不需要对失败的结果进行处理,只需要在最后加一个catch方法去处理失败的结果。
// 解决异常穿透 在then方法前面添加
// 异常穿透的中途如果有失败的话,但是可以then方法里面只写了onResolved方法
// 没有写onRejected方法,那么如果此时发生了错误,由于没有定义onRejected
// 就会产生undefined,会报错,故需要自己检测 如果没有定义需要自己定义
if(typeof onRejected !== 'function') {
onRejected = reason => {
throw reason;
}
}
// 同理,onResolved也是如此
if(typeof onResolved !== 'function') {
// 此处是简写形式,value => return value;
onResolved = value => value;
}
Promise.prototype.catch = function(onRejected) {
return this.then(undefined,onRejected);
}
Promise.resolve函数
返回的是一个成功的promise对象
Promise.resolve = function(value) {
return new Promise((resolve,reject) => {
if(value instanceof Promise) {
value.then(
v => resolve(v),
r => reject(r)
);
} else {
resolve(value);
}
})
}
Promise.reject
返回一个失败的promise对象
Promise.reject = function(value) {
return new Promise((resolve,reject) => {
reject(value);
});
}
Promise.all
Promise.all = function(promises) {
// 成功的个数
let count = 0;
// 成功的结果
let arr = [];
return new Promise((resolve,reject) => {
for(let i = 0;i<promises.length;i++) {
promises[i].then(
v => {
count++;
arr[i] = v;
if(count === promises.length) {
resolve(arr);
}
},
r => reject(r)
);
}
})
}
Promise.race
Promise.race = function(promises) {
return new Promise((resolve,reject) => {
for(let i = 0;i<promises.length;i++) {
promises[i].then(
v => resolve(v),
r => reject(r)
);
}
})
}
回调函数异步执行
let p = new Promise((resolve,reject) => {
resolve('ok');
console.log(111);
})
p.then(
v => console.log(222)
)
console.log(333);
// 输出:111 333 222
// 因为then里面指定的回调是异步执行的,等同步执行完毕之后它才会执行
解决:
在then方法和callback方法中的立即执行加上一个定时器
// 加上定时器是因为要使得then指定的回调是异步执行的
setTimeout(callback(onResolved),0);
async与await
async函数
1、async函数的返回值是promise对象
2、promise对象的结果由async函数执行的返回值决定
async有点像then方法,它的返回规则与then方法中的规则一致
如果async函数中返回的是非promise 则成功,且值为该值
否则返回该promise的执行结果
await表达式
await返回的是promise成功的值,功能主要是对他后面接的promise对象的成功值进行获取。
1、await右侧的表达式一般为 promise 对象, 但也可以是其它的值
2、如果表达式是 promise 对象, await 返回的是 promise 成功的值
3、如果表达式是其它值, 直接将此值作为 await 的返回值
ps:
await 必须写在 async 函数中, 但 async 函数中可以没有 await
如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
例子
使用async和await对多个文件值进行获取
// 对多个文件进行值的获取
// 还是使用nodejs的fs读取模块
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);
// 声明main方法为一个异步方法
// 在里面使用await接收文件结果
async function main() {
try{
let data1 = await mineReadFile('./content.txt');
data1 = data1.toString();
let data2 = await mineReadFile('./content2.txt');
data2 = data2.toString();
let data3 = await mineReadFile('./content3.txt');
data3 = data3.toString();
console.log(data1 + data2 + data3);
}
catch(e) {
console.log(e);
}
}
main()
使用async和await进行Ajax发送请求
需要将ajax封装成一个Promise对象(这一步就是axios做的事情,它是基于promise的基本方式对ajax进行封装的包)