Promise入门
(一)Promise的理解和简单使用
一、Promise是什么?
1、理解
(1)抽象表达
Promise是一门新的技术(ES6规范);是js中异步编程的新解决方案(旧方案中是单纯使用回调函数)
异步编程:
1.fs 文件操作
require(‘fs’).readFile(’./index.html’, (err, data)=>{})
2.数据库操作
3.AJAX
$.get(’/server’, (data)=>{})
4.定时器
setTimeout(()=>{})
(2)具体表达
- 从语法上说:Promise是一个构造函数
- 从功能上说:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值。
2、为什么要用Promise
(1)指定回调函数的方式更加灵活
- 旧方法:必须在启动异步任务前指定回调函数
- promise:启动异步任务=>返回promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定[多个]回调函数)
(2)支持链式调用,可以解决回调地狱问题
-
回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
-
回调地狱缺点?
不便于阅读(很多缩进)
不便于异常处理
-
解决方案?
Promise链式调用
二、Promise对象属性
1、状态属性
promise状态其实是Promise实例对象的一个属性 PromiseState
- pending 未决定的
- resolved / fulfilled 成功
- rejected 失败
2、状态改变
- pending -> resolved
- pending -> rejected
只有这两种状态改变,且每个promise对象只能改变一次;无论变为成功还是失败,都会有一个结果数据。
- 成功的结果数据一般称为value
- 失败的结果数据一般称为reason
3、结果值
实例对象的另一个属性 PromiseResult
,保存的是异步任务对象【成功 / 失败】的结果。
可以通过resolve
和reject
两个函数对值进行修改,然后在then方法进行调用是,传给value/reason
三、Promise基本使用
1、Promise初体验
(1)抽奖系统(定时器)
使用普通回调函数实现功能:
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
/*
点击按钮,1s后显示是否中奖(30%概率中奖)
*/
const btn = document.querySelector(".btn");
btn.addEventListener("click", function () {
// 定时器
setTimeout(() => {
// 30%
// 随机获取一个1-100的数字
let n = rand(1, 100);
// 判断如果在1-30之间则中奖
if (n <= 30) {
alert("恭喜你中奖了!");
} else {
alert("再接再厉");
}
}, 1000);
使用promise实现:
-
Promise构造函数调用时,有两个函数作为参数:
resolve 解决 => 将promise对象的状态设置为成功
reject 拒绝 => 将promise对象的状态设置为失败
-
promise对象会调用then方法,里面同样也接收两个函数作为参数:分别是对象成功/失败时调用的函数
// 使用promise实现
// resolve 解决 函数类型的数据
// reject 拒绝 函数类型的数据
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// 30%
// 随机获取一个1-100的数字
let n = rand(1, 100);
// 判断如果在1-30之间则中奖
if (n <= 30) {
resolve(); //将promise对象的状态设置为 成功
} else {
reject(); //将promise对象状态设置为 失败
}
}, 1000);
});
// 调用then方法,同样接收两个函数作为参数;分别代表对象成功时调用的函数和失败时调用的函数
p.then(() => {
alert("恭喜你中奖了!");
}, () => {
alert("再接再厉");
});
同时,resolve和reject函数可以返回结果值供then中函数作为参数使用:
这里将n作为结果封装在resolve和reject中,并分别传递给value和reason
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// 30%
// 随机获取一个1-100的数字
let n = rand(1, 100);
// 判断如果在1-30之间则中奖
if (n <= 30) {
resolve(n); //将promise对象的状态设置为 成功
} else {
reject(n); //将promise对象状态设置为 失败
}
}, 1000);
});
// 调用then方法,同样接收两个函数作为参数;分别代表对象成功时调用的函数和失败时调用的函数
p.then((value) => {
alert("恭喜你中奖了!您的中奖数字为" + value);
}, (reason) => {
alert("再接再厉 您的号码为" + reason);
});
(2)文件操作
普通回调函数实现:
const fs = require('fs');
//回调函数的形式
fs.readFile('Promise/resource/content.txt', (err, data) => {
if (err) throw err;
console.log(data.toString());
});
使用Promise形式封装:
const fs = require('fs');
// 使用Promise形式进行封装
let p = new Promise((resolve, reject) => {
fs.readFile('Promise/resource/content.txt', (err, data) => {
// 如果出错
if (err) reject(err);
// 成功
resolve(data);
});
});
// 调用then
p.then(value => {
console.log(value.toString());
}, reason => {
console.log(reason);
})
(3)AJAX请求
普通方法实现ajax请求:
// 接口地址:https://api.apiopen.top/getJoke
const btn = document.querySelector('.btn');
btn.addEventListener('click', function () {
const xhr = new XMLHttpRequest();
xhr.open('GET', "https://api.apiopen.top/getJoke");
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
} else {
console.log(xhr.status);
}
}
}
})
使用Pomise对以上ajax请求进行封装:
// 接口地址:https://api.apiopen.top/getJoke
const btn = document.querySelector('.btn');
btn.addEventListener('click', function () {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', "https://api.apiopen.top/getJoke");
xhr.send();
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.warn(reason);
});
})
2、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('Promise/resource/content.txt')
.then(value => {
console.log(value.toString());
}, reason => {
console.log(reason);
})
(2)util.promisify方法
该方法会返回一个promise对象:
/**
* util.promisify方法
*/
// 引入util模块
const util = require('util');
// 引入fs模块
const fs = require('fs');
// 返回一个新的函数
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('Promise/resource/content.txt').then(value => {
console.log(value.toString());
})
(3)ajax
封装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://api.apiopen.top/getJoke').then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
四、Promise基本流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-miedzRC8-1634024002900)(Promise入门.assets/image-20211010163606093.png)]
(二)如何使用Promise?
一、API
1、Promise构造函数:Pomise(excutor){}
实例化对象时,需要一个参数,这个参数是一个函数执行器excutor{}
。
excutor函数
:执行器(resolve, reject)=>{}
resolve函数
:内部定义成功时调用的函数value => {}
reject函数
:内部定义失败时调用的函数reason => {}
excutor
会在Promise内部立即同步调用,异步操作在执行器中执行。
比如,以下代码:
const p = new Promise((resolve, reject) => {
console.log(111);
});
console.log(222);
//output:
111
222
2、Promise.prototype.then方法:(onResolved, onRejected) => {}
onResolved函数
:成功的回调函数(value) => {}
onRejected函数
:失败的回调函数(reason) => {}
指定用于得到成功value的成功回调和用于失败reason的失败回调。返回一个新的promise对象。
3、Promise.prototype.catch方法:(onRejected) => {}
也就相当于失败的回调函数:
p.catch(reason => {
console.log(reason);
});
4、Promise.resolve方法:(value) => {}
这个方法比较特殊,并不是属于实例对象的,而是属于Promise这个函数对象。
接收一个参数value,返回一个成功/失败的promise对象。
- 如果传入的参数是一个非Promise类型的对象,则返回结果为成功promise对象:
let p1 = Promise.resolve(521);
console.log(p1);
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: 521
- 如果传入的参数是一个Promise对象,则参数的结果决定了resolve的结果:
// 里面的参数是成功的,所以p2也是成功的
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK');
}))
console.log(p2);
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: "OK"
// 里面的参数是失败的,所以p2也是失败的
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}))
console.log(p2);
//[[Prototype]]: Promise
//[[PromiseState]]: "rejected"
//[[PromiseResult]]: "Error"
5、Promise.reject方法
同样也是Promise函数对象的方法,而不是实例对象的方法。
不论传入什么参数,结果都是rejected,就算传入的参数是一个成功的promise对象。
let p = Promise.reject(new Promise((resolve, reject) => {
resolve('resolve');
}));
console.log(p);
//[[Prototype]]: Promise
//[[PromiseState]]: "rejected"
//[[PromiseResult]]: "Promise"
6、Promise.all方法:(promises) => {}
promises
是包含n个promise的数组,返回一个新的promise,只要所有的promise都成功才算成功,只要有一个失败了就直接失败。
promise对象全都成功:
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
/*
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(3)
0: "OK"
1: "Success"
2: "Yeah"
*/
如果有失败的promise对象:
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "Error"
09-PromiseAPI-all.html:1 Uncaught (in promise) Error
*/
7、Promise.race方法
基本与all方法一致,但race方法是第一个完成的promise的结果状态就是最终的结果状态。
二、Promise的几个关键问题
1、如何改变Promise对象状态
(1)resolve函数
// 1.调用resolve函数
resolve('OK'); // pendding->resolved/fulfilled
// Promise {<fulfilled>: 'OK'}
(2)reject函数
// 2.调用reject函数
reject('Error'); // pendding->rejected
//Promise {<rejected>: 'Error'}
//10-状态修改.html:18 Uncaught (in promise) Error
(3)抛出错误
// 3.抛出错误
throw '出问题了';
//Promise {<rejected>: '出问题了'}
//10-状态修改.html:13 Uncaught (in promise) 出问题了
这里的报错都是因为没有指定失败回调导致的。
2、一个promise指定多个成功/失败回调函数,都会调用吗
当发生状态改变时,指定的回调函数都会被调用:
let p = new Promise((resolve, reject) => {
resolve('OK');
});
// 指定回调
p.then(value => {
console.log(value);
})
// 再次指定回调
p.then(value => {
alert(value);
})
以上代码中,因为有resolve('OK')
,状态由pending->resolved
,所以下面的两个回调函数都会被执行。
但如果把resolve注释掉,也就是状态没有发生改变,那么指定的回调函数就不会被执行。
3、改变promise状态和指定回调函数谁先执行
(1)都有可能,正常情况下是先指定函数再改变状态,但也可以先改变状态再指定回调函数。
(2)先指定回调再改变状态
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(value);
}, reason => { });
比如在执行器中,改变状态时使用一个定时器。
(3)如何先改变状态再指定回调
- 执行器中直接调用resolve()/reject()
- 延迟更长时间才调用then()
(4)什么时候才能得到数据(也就是说回调函数什么时候执行)
- 如果先指定回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变状态,那当指定回调时,回调函数就会调用,得到数据
4、promise.then()返回的新promise结果状态由什么决定
(1)简单表达
由then()指定的回调函数执行结果决定
(2)详细表达
- 如果抛出异常,新promise变为rejected,且reason为抛出的异常
- 如果返回的是非promise的任意值,新promise变为resolved,且value为返回的值
- 如果返回的是另一个新的promise,此promise的结果就会成为新promise的结果
5、promise如何串联多个操作任务
(1)promise的then()返回一个新的promise,可以看成then() 的链式调用
(2)通过then的链式调用串联多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resovle, reject) => {
resovle("success");
});
}).then(value => {
console.log(value); //1s后输出success
}).then(value => {
console.log(value); //success后输出undefined
})
6、异常穿透
(1)当使用promise的then链式调用时,可以在最后指定失败的回调
(2)前面任何操作出了异常,都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('error');
}, 1000);
});
p.then(value => {
console.log(111);
throw '出错啦';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
7、中断promise链
(1)当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
(2)办法:在回调函数中返回一个pendding状态的promise对象【因为状态没有改变,所以后面的then方法都不能执行】
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
// 中断promise链--返回一个pendding状态的promise对象
return new Promise(() => { });
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
在回调函数中返回了一个pendding状态的promise对象,就会导致没有状态改变,也就是后面的then方法都不能被调用,从而实现中断promise链。