测试环境为Node.js/谷歌浏览器,如若没有声明,默认环境为Node.js
在Promise
语法出现前,我们是这样使用具有依赖关系的异步函数的,以读取文件内容为例:
let fs = require("fs");
fs.readFile('./1.txt','utf-8',(err, result1) => {
console.log(result1);
fs.readFile('./2.txt','utf-8',(err, result2) => {
console.log(result2);
fs.readFile('./3.txt','utf-8',(err, result3) => {
console.log(result3);
});
});
});
这就是我们通常所说的回调地狱,在结构上有着极不便于阅读的缺点,为此Promise的出现为我们在结构上做了极大的优化
大纲
Promise的初使用
Promise是什么?
对象
promise
对象通过new
直接创建
//let p = new Promise(); 没有参数会报错
let p = new Promise((resolve,reject) => {}); //通常传入一个匿名函数,函数参数一般为resolve和reject
试着打印一下这个对象
let p = new Promise((resolve,reject) => {});
console.log(p); //=> Promise { <pending> }; :Node.js
console.log(p); //=> Promise {<pending>}; :谷歌浏览器
Promise的状态
Promise一共有三种状态,上面打印的是Promise的第一种状态pending(等待)
以下是Promise的三种状态,任何一个Promise对象的状态有且仅有以下一种
- pending(等待)
- resolve(成功)
- reject(拒绝)
打印一个Promise对象
在谷歌浏览器分别实现为
Promise { <pending> }
Promise { <fullfilled>: (参数) }
Promise { <rejected>: (参数) }
在Node.js分别实现为
Promise { <pending> }
Promise { (参数) }
Promise { <rejected>: (参数) }
在Node.js中分别简单地实现Promise的三种状态
let p1 = new Promise((resolve, reject) => {
//不做任何处理,默认返回pending状态的Promise
});
console.log(p1); //=> Promise { <pending> }
let p2 = new Promise((resolve, reject) => {
resolve("second"); //用resolve函数返回的都是resolve状态的Promise,参数为second
});
console.log(p2); //=> Promise { 'second' }
let p3 = new Promise((resolve, reject) => {
reject("thrid"); //用reject函数返回的都是reject状态的Promise,参数为thrid
}); //因为抛出了拒绝状态而没有处理,所以这里会报错,可以暂时不用管,不影响我们的结果
console.log(p3); //=> Promise { <rejected> 'third' }
那么Promise的状态有什么用呢?
传递给后面的then函数处理携带的参数
then()
关于它的使用只需要记住一点:
then方法是跟在Promise对象后面使用的
关于then的特点,需要记住一下几点(最后会做验证)
1.then方法处理上一个Promise的状态,并返回一个新的resolve状态(如果上一个Promise状态不为pending的话,否则返回pendding状态的Promise),参数为undefined
2.then方法默认返回的也是一个Promise对象,因此then是可以链式使用的
2.如果then方法没有处理Promise的状态,那么这个Promise的状态会继续向下传递,给下面的then处理
then方法可以接收一个或者两个函数,一般都是匿名函数
/*接收两个函数,这两个函数可以分别处理上一个Promise的状态*/
new Promise((resolve, reject) => {
}).then(value => { //假如Promise的状态为resolve,那么value为对应resolve参数的值
console.log(value);
},reason => { //假如Promise的状态为reject,那么value为对应reject参数的值
console.log(reason);
})
/*接收一个函数*/
new Promise((resolve, reject) => {
}).then(value => { //假如Promise的状态为resolve,那么value为对应resolve参数的值
console.log(value);
})
/*或者*/
new Promise((resolve, reject) => {
}).then(null,reason => { //假如Promise的状态为reject,那么value为对应reject参数的值
console.log(reason);
})
来一个实例
说了这么多,是时候上一个例子了,还是以读一个文件为例子
事先向同目录下的text.txt
文件内写入Hello Promise
/*读取文件操作*/
let fs = require("fs");
new Promise((resolve, reject) => {
fs.readFile("./text.txt","utf-8", (err, result) => {
if(!err) {
resolve(result);
}else {
reject(err);
}
})
}).then(value => {
console.log(value); //=> Hello Promise
},reason => {
console.log(reason);
})
接下来模拟文件找不到的情况,可见Promise语法对其处理良好:
/*读取文件操作*/
let fs = require("fs");
new Promise((resolve, reject) => {
fs.readFile("./123.txt","utf-8", (err, result) => { //123.txt为不存在的文件
if(!err) {
resolve(result);
}else {
reject(err);
}
})
}).then(value => {
console.log(value);
},reason => {
console.log(reason); //=> Error: ENOENT: no such file or directory, open 'C:\Users\Admin......\123.txt'
})
以上所学的不用Promise语法我们也完全可以实现,但是Promise做的就只有这么多了吗?
Promise不止如此
接下来我们引入宏队列与微队列的概念
在学习Js同步和异步的时候,我们知道像setTimeout
等异步函数会被放在一个分线程,等主线程的函数执行完了才执行异步函数,如下:
事实上,分线程还可以进一步划分为宏队列与微队列,他们也有有不同的执行优先级
微队列的函数是优于宏队列执行的
现在我们有三个执行等级了:
主线程 > 微队列 > 宏队列
注意:
上图所说的微队列里面的是Promise的回调函数,也就是执行完Promise对象后的then()方法
我们来一个测试:
new Promise((resolve, reject) => {
console.log("A"); //1. 按照从上到下的顺序,首先输出A
resolve("C");
}).then(value => {
console.log(value); //3.这里才是Promise的回调,属于微队列,输出resolve的参数"C"
},reason => {
console.log(reason);
})
setTimeout(()=> {
console.log("D"); //4.setTimeout属于宏队列,最后执行
},0);
console.log("B"); //2. 按照从上到下的顺序,其次输出B
/*
A
B
C
D
*/
宏任务的提升原来是误解
我们再来看一个例子,不知道是否与你想的一致(许多人会误解为ABDC)
new Promise((resolve, reject) => {
setTimeout(()=> {
resolve(); //微任务是在宏任务执行过程中创建出来的,因此先输出C再输出D
console.log("C");
},0)
console.log("A");
}).then(value=> {
console.log("D")
},reason => {
console.log(reason)
})
console.log("B");
/*
A
B
C
D
*/
现在,我们知道Promise也有异步机制,接下来你就会发现Promise能很好的解决回调地狱的问题
封装Promise
let fs = require("fs");
function file(url, code="utf-8") { //封装Promise,其核心是返回Promise对象,让其后面可以调用then方法
return new Promise((resolve, reject) => {
fs.readFile(url, code, (err, result) => {
if(!err) {
resolve(result);
}else {
reject(err);
}
})
})
}
/*
*我们在txt文件夹下准备三个文件1.txt、2.txt、3.txt
*内容分别为: "第一个文件输出了","第二个文件输出了","第三个文件输出了"
*/
file("../txt/1.txt")
.then(value => {
console.log(value);
return file("../txt/2.txt");
}).then(value => {
console.log(value);
return file("../txt/3.txt");
}).then(value => {
console.log(value);
}).catch(err => {
console.log(err);
})
/*
第一个文件输出了
第二个文件输出了
第三个文件输出了
*/
你需要知道的一些细节
现在我们已经掌握了Promise解决回调地狱的问题,但是还有许多关于Promise的细节需要我们了解
上文提到Promise的细节,现在我们一一来验证
1.then方法处理上一个Promise的状态,并返回一个新的resolve状态(如果上一个Promise状态不为pending的话,否则返回pendding状态的Promise),参数为undefined
看着虽然复杂,下面一一进行举例:
/*没有then方法处理Promise状态*/
let p1 = new Promise((resolve, reject) => {
resolve("hello"); //调用了resolve,初始状态为resolve
})
console.log(p1);
/*
Promise { 'hello' }
*/
/*then处理Promise<resolve>的状态*/
let p2 = new Promise((resolve, reject) => {
resolve("hello"); //调用了resolve,初始状态为resolve
}).then(value => {
console.log("resolve处理完毕")
},reason => {
})
setTimeout(() => { //在微任务全部执行完毕后,也就是then执行完毕后再打印p2
console.log(p2);
})
/*
resolve处理完毕
Promise { undefined } //处理后返回一个新的resolve状态,参数为undefined
*/
/*then处理Promise<reject>的状态*/
let p3 = new Promise((resolve, reject) => {
reject("hello"); //调用了reject,初始状态为reject
}).then(value => {
console.log("resolve处理完毕")
},reason => {
console.log("reject处理完毕")
})
setTimeout(() => { //在微任务全部执行完毕后,也就是then执行完毕后再打印p2
console.log(p3);
})
/*
reject处理完毕
Promise { undefined } //处理后还是返回一个新的resolve状态,参数为undefined
*/
/*then不会处理pending状态的promise,因此pending状态继续向下传递*/
let p4 = new Promise((resolve, reject) => {
//没有调用resolve或者reject,状态为pending
}).then(value => {
console.log("处理完毕")
},reason => {
})
setTimeout(() => { //在微任务全部执行完毕后,也就是then执行完毕后再打印p2
console.log(p4);
})
/*
Promise { <pending> }
*/
2.then方法默认返回的也是一个Promise对象,因此then是可以链式使用的
第一点其实已经表明了:then方法默认返回的也是一个Promise对象,在我们输出p1、p2、p3、p4的时候,看到的就是经过then方法处理过状态的一个新的Promise
关于第二点,只需要记住一点:在多个then链式使用时,某一个then返回的Promise的状态取决于上一个Promise的状态和这个then有没有处理上一个Promise的状态。
举例:
/*某一个then返回的Promise的状态取决于上一个Promise的状态(---resolve状态)和这个then有没有处理上一个Promise的状态(---没有)*/
let p1 = new Promise((resolve, reject) => {
resolve("hello");
})
let p2 = p1.then();
setTimeout(() => {
console.log(p1);
console.log(p2);
})
/*
Promise { 'hello' } //初始状态为:<resolve>:"hello"
Promise { 'hello' } //then不做处理,返回与上一个Promise相同状态:<resolve>:"hello"
*/
/*某一个then返回的Promise的状态取决于上一个Promise的状态(---resolve状态)和这个then有没有处理上一个Promise的状态(---处理了)*/
let p3 = new Promise((resolve, reject) => {
resolve("hello");
})
let p4 = p3.then(value => {},reason => {});
setTimeout(() => {
console.log(p3);
console.log(p4);
})
/*
Promise { 'hello' } //初始状态为:<resolve>:"hello"
Promise { undefined } //then做处理,返回<resolve>:undefined
*/
/*then不能处理pending状态的Promise,因此这个状态会一直传递下去*/
let p5 = new Promise((resolve, reject) => {
})
let p6 = p5.then()
.then()
.then();
setTimeout(() => {
console.log(p5);
console.log(p6);
})
/*
Promise { <pending> }
Promise { <pending> }
*/
3.如果then方法没有处理Promise的状态,那么这个Promise的状态会继续向下传递,给下面的then处理
第三点比较好理解,如下:
let p1 = new Promise((resolve, reject) => {
reject("error"); //初始状态为reject
})
let p2 = p1.then()
.then()
.then(); //没有用then处理reject状态
let p3 = p2.then(value => {},reason => {}); //then处理了p2的reject状态,返回给p3
setTimeout(() => {
console.log(p1);
console.log(p2);
console.log(p3);
})
/*
Promise { <rejected> 'error' } //初始状态为reject
Promise { <rejected> 'error' } //没有用then处理reject状态,因此p2接收到传递下来的reject状态
Promise { undefined } //then处理了p2的reject状态,默认传递给下一个的状态为Promise { undefined }(特点一)
*/
如果你看了上述例子却还是很懵,那么你需要知道另一件事,或许你就能明白
Promise的状态不可变性
一旦确定了某个状态(不为pending,可以是resolve或者reject),那么这个Promise对象的状态将不会改变
有的小伙伴感到很疑惑,状态不是可以处理吗,然后可以改变吗?我们来看一个例子你就明白了
let p1 = new Promise((resolve, reject) => {
resolve("hello")
})
console.info("处理前p1的状态: ")
console.log(p1);
let p2 = p1.then(
value => {
console.log(value);
},reason => {
console.log(reason);
});
setTimeout(() => {
console.log('------------------------------------------')
console.info("处理后p1的状态: ");
console.log(p1);
console.info("用then处理p1后,p1.then()的状态: ")
console.log(p2);
})
/*
处理前p1的状态:
Promise { 'hello' }
hello
------------------------------------------
处理后p1的状态:
Promise { 'hello' } //状态不可变性
用then处理p1后,p1.then()的状态:
Promise { undefined } //实际上,上面我们看到的状态改变都是then()的返回值,并不是上一个Promise的状态
*/
Promise的异常处理:
我们知道使用reject()
可以使Promise的状态由<pending>
变为<reject>
状态,并向下转递此状态
但其实还有几种方式都可以使Promise的状态由<pending>
变为<reject>
状态
let p1 = new Promise((resolve, reject) => {
//reject("error");
//throw new Error("发生错误"); //抛出异常,可以抛出Error的实例,或者继承Error类的类的实例
//Hd +; //语法错误
}).then(value => {
console.log(value);
},reason => {
console.log("err: " + reason); //上面三种错误都可以被此条语句捕获处理
})
catch:
catch方法同then方法的第二个函数的作用是一样的,我们通常用来对链式then方法调用产生的语法错误进行统一处理
在封装Promise的时候,我们用到了catch,现在我们对catch做一个优化
let fs = require("fs");
function file(url, code="utf-8") { //封装Promise,其核心是返回Promise对象,让其后面可以调用then方法
return new Promise((resolve, reject) => {
fs.readFile(url, code, (err, result) => {
if(!err) {
resolve(result);
}else {
reject(`打开文件${url}错误`); //明确某一个文件错误
}
})
})
}
/*
*我们在txt文件夹下准备三个文件1.txt、2.txt、3.txt
*内容分别为: "第一个文件输出了","第二个文件输出了","第三个文件输出了"
*/
file("../txt/1.txt")
.then(value => {
console.log(value);
return file("../txt/22.txt"); //故意输错路径
}).then(value => {
console.log(value);
return file("../txt/3.txt");
}).then(value => {
console.log(value);
}).catch(err => {
console.log(err); //捕获所有错误并处理
})
/*
我是第一个
打开文件../txt/22.txt错误
*/