Promise 是ES6引入的异步编程的新解决方案。
语法上Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
Promise 构造函数: Promise(excutor){}
Promise.prototype.then 方法
Promise.prototype.catch 方法
Promise
实例化 Promise 对象;
Promise 回调函数的两个形参(默认规范): resolve 和 reject ,也是两个函数;调用这两形参函数分别代表构造出的新对象的两种状态:成功 和 失败;
const p = new Promise(function(resolve,reject){
setTimeout(function(){
let data = "成功获取用户数据";
resolve(data); // 调用 resolve 函数,在then 中对应 value,data 作为参数传给了 value
},1000);
});
// 调用 promise 里的 then 方法,这个方法有两个回调函数作为参数,分别对应 resovle 和 reject 两种状态
// 成功的形参叫 value ,失败的形参叫 reason,也是默认规范
p.then(function(value){ // Promise 中调用了 resolve ,then 中对应的调用 value
console.log(value); // 成功获取用户数据 会显示 resolve 返回的数据;
},function(reason){
});
Promise实例化的 p 对象有三种状态:初始化,成功 和 失败;
当调用 resolve 函数时,即标识 p 对象的状态为成功时,将在 then 方法里调用第一个参数函数,即 value 对应的函数;
当调用 reject 函数时,即标识 p 对象的状态为失败时,那 then 方法将执行第二个回调函数,即 reason 对应的函数;
上面实例中,p 对象调用了 resolve 并返回了 data ,那么 then 方法中会执行第一个函数,并把 data 作为参数,传给了 value ,最后输出;
如果异步函数 setTimeout 中调用了 reject 函数呢?
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
let err = "数据读取失败";
reject(err); // 调用了 reject 函数,相当于设定 p 对象的状态为失败,then 方法会执行第二个函数
}, 1000);
});
p.then(function (value) {
console.log(value);
}, function (reason) { // 因调用了 reject 函数,所以 then 方法执行第二个函数
console.log(reason); // 数据读取失败
});
通过这种方式 ,把异步任务封装在 Promise 的对象 p 里面; 通过 resolve 和 reject 函数来改变 p 对象的状态,从而影响 then 方法里的回调函数;即“成功”就调第一个函数,“失败”就调第二个函数;
官方说法,一个 Promise 必然处于以下几种状态之一:
待定(pending):初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled):意味着操作成功完成。
已拒绝(rejected):意味着操作失败。
可在视频教程中看到的成功状态可能是:resolved ,而不是 fulfilled ,暂时理解意思相同;
Promise封装读取文件
需要 Node 环境,先引入 fs 模块,再调用方法读取文件;
const fs = require('fs');
fs.readFile('./res/为学.md',(err,data)=>{
if(err) throw err;
console.log(data.toString());
});
通过 Promise 来封装:
const fs = require('fs');
const p = new Promise(function (resolve, reject) {
// 读取文件是异步操作
fs.readFile('./res/为学.md', (err, data) => {
// 如果失败,改变为失败状态 reject,可设置失败值为 err
if (err) reject(err);
// 如果成功,改变状态为成功
resolve(data);
});
});
// 不是一定要用 value 和 reason 作为形参,只是约定俗成的命名
p.then(function (value) {
console.log(value.toString());
}, function (reason) {
console.log("读取失败");
});
明明几行代码就能做好的事情,还用费这么大劲?
因为单个任务看不出来,要是有多个异步任务,尤其是有先后顺序的异步任务时,使这种方法会不断缩进,产生回调地狱问题,使用 Promise 就不会出这个问题。
Promise封装AJAX
创建原生的AJAX
//1, 创建对象
const xhr = new XMLHttpRequest();
// 2, 初始化
xhr.open("get", "https://api.apiopen.top/api/sentences");
// 3, 发送
xhr.send();
// 4, 事件绑定,处理响应结果
xhr.onreadystatechange = function () {
// 判断
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 表示成功
console.log(xhr.response);
} else {
// 表示失败
console.log(xhr.status);
}
}
}
用 Promise 封装 AJAX
const p = new Promise(function(resolve,reject){
const xhr = new XMLHttpRequest();
xhr.open('get','https://api.apiopen.top/api/sentences');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status <300){
resolve(xhr.response);
}else{
reject("xhr.status :" + xhr.status);
}
}
}
});
// 指定回调
p.then(function(value){
console.log(value);
},function(reason){
console.log(reason);
});
Promise的then方法
先创建 promise 对象,再调用 then 方法:
const p = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve('获取用户数据');
},1000);
});
p.then((value)=>{
console.log(value); // 获取用户数据
},(reason)=>{
console.warn(reason);
});
若状态为成功,则调用 then 方法的第一个函数;
若状态为失败,则调用 then 方法的第二个函数;
const p = new Promise(function(resolve,reject){
setTimeout(function(){
// resolve('获取用户数据');
reject('出错了');
},1000);
});
p.then((value)=>{
console.log(value);
},(reason)=>{
console.warn(reason); // 出错了
});
改为 reject 后,状态为失败,控制台正常输出数据,没有问题;
then() 方法的返回结果,也是一个 promise 对象;状态由回调函数的执行结果决定;
把返回结果赋给 result ;
const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});
// 把 then 的返回结果赋值给 result
const result = p.then((value)=>{
console.log(value); // 获取用户数据,这里是正常输出
},(reason)=>{
console.warn(reason);
});
console.log(result);
result 也是个 promise 对象;也有状态,当前状态是: fulfilled ,也就是成功;当前无返回值,也就是 undefined;
情况1:如果回调函数返回的结果是 “非Promise” 类型的值,那么 result 状态为成功,返回值就是对象的值;
const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});
const result = p.then((value)=>{
console.log(value); // 获取用户数据
return "abc"; // 返回 abc
},(reason)=>{
console.warn(reason);
});
console.log(result);
"abc" 是字符串结果,是“非Promise”值,那么状态为成功 "fulfilled",result 的值就是 "abc";
如果不写 return ,即返回 undefined ,也是 非Promise 值,所以状态也为 "fulfilled";
情况2:如果回调函数返回的结果 是Promise对象,那内部返回的状态即为 result 的状态;
const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});
const result = p.then((value)=>{
console.log(value);
return new Promise((resolve,reject)=>{ // 返回了 promise 对象
resolve('OK'); // 状态为 成功
});
},(reason)=>{
console.warn(reason);
});
console.log(result);
then 方法 里返回了一个 Promise 对象,那么 result 的状态就由这个对象的状态决定,当前是成功状态,所以 result 的状态也为成功,值即为返回的值:"OK";
如果返回的是失败呢?
// resolve('OK');
reject('error'); // 改为失败
情况3:抛出错误,那状态也是 失败;
// resolve('OK');
// reject('error');
throw new Error('出错了');
总结:既然 then() 方法返回的也是 Promise对象,也有对象的状态,所以 then 方法就可以链式调用;
then链式调用
根据then的特点,可以使用这样的链式调用:
p.then((value)=>{},(reason)=>{}).then((value)=>{},(reason)=>{}).then(...)
也可以不用都写 reason:
p.then((value)=>{}).then((value)=>{});
可以在then方法里再写异步任务,一个个链起来,从而避免了回调地狱问题;
Promise实践练习
读取多个文件:
const fs = require('fs');
fs.readFile('./res/笔记1.md',(err,data1)=>{
fs.readFile('./res/笔记2.md',(err,data2)=>{
fs.readFile('./res/笔记3.md',(err,data3)=>{
let result = data1 + "\r\n" + data2 + "\r\n" + data3;
console.log(result);
});
});
});
平时可能不这么写,但如有层层递进的需求时,就会形成这种回调地狱;
换成 promise 的形式:
const fs = require('fs');
const p = new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记1.md',(err,data)=>{
fulfill(data);
});
});
p.then((value)=>{
return new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记2.md',(err,data)=>{
data = [value,data];
fulfill(data);
});
});
}).then((value)=>{
return new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记3.md',(err,data)=>{
// 压入
value.push(data);
fulfill(value);
});
});
}).then((value)=>{
console.log(value.join('\r\n'));
});
通过then方法,把多个异步任务串联起来,看起来有些啰嗦,没有回调地狱的问题;
Promise对象的catch方法
这个方法用来指定 Promise对象失败的回调;
const p = new Promise((fulfill,reject)=>{
setTimeout(()=>{
// 设置 p 对象的状态为失败,并设置失败的值
reject("出错了");
},1000);
});
p.then(value=>{},reason=>{
console.error(reason);
});
then 有两个回调函数,第一个处理成功的状态;第二个处理失败的状态;也可以只写第一个;
所以,专门处理失败的回调,就可以使用 catch 方法了;
p.catch(reason=>{
consol.error(reason);
});
catch 方法相当于 then 的语法糖,语法糖是用简练的形式表达较复杂的含义,相当于汉语中的“成语”;
从用法上:
和
这两个等价;