目录
1.什么是promise
2.关于promise对象
3.promise常用API
4.promise链式调用
5.async和await语法糖
1.什么是promise
众所周知,在以往,我们处理异步操作主要是通过回调函数处理
setTimeout(()=>{},100);
例如这个定时器,异步操作会通过时间循环队列实现.
但是比如说我们想要执行多个嵌套的异步操作就会变的很麻烦
而且可读性非常差,代码像是很多层循环那样,向右延申,这种情况我们称之为"回调地狱"
所以我们引入了promise这种东西,来处理回调地狱问题,promise对象会把异步操作封装在then函数的不同操作中,这样就实现了链式的异步调用
举个例子,还是定时器
const p=new Promise((resolve,reject)=>{
setTimeout(()=>{
let n=2;
if(n===1){
resolve(n);
}else{
reject(n);
}
},10);
})
p.then((value)=>{ },(reason)=>{ });
这样可以将嵌套执行的异步操作放到后面,这就是promise的作用
2.关于promise对象
promise对象用来封装异步操作,并且可以根据then来执行嵌套的异步操作,增强额可读性
(0)promise的三种状态以及形成方法
promise有三种状态,分别是:
pending挂起状态,主要用于后面的promise链终止
fulfilled 成功状态
rejected失败状态
这三种状态的转化关系一般是由pending状态转化为其他两种,并且在转化完成以后执行相关的回调函数,详见下方
(1)promise的构造
promise最常用的构造方法是
const p=new Promise((resolve,reject)=>{
......
})
这样就会返回一个对应的promise对象,对于这个对象可以调用then方法
其中,传入的回调函数中有两个参数,这两参数我们按习惯命名为resolve还有reject,这两个函数可以在Promise调用构建的时候执行,分别代表不同的状态
resolve("成功的参数");
reject("失败的参数");
这两个函数调用以后会改变Promise对象的状态,分别会改变成为fulfilled(成功状态,也叫做resolved)和rejected(失败状态).并且在随后的then中,选择对应的函数进行执行(then中的第一个函数对应成功状态,第二个函数对应失败状态)
另外,promise也具有其他的生成方式,比如直接调用resolve和reject,会直接生成状态为fulfilled和rejected状态的Promise对象
const a=Promise.resolve(value);
const b=Promise.reject(reason);
其实每次调用then,catch的时候也会创建一个新的promise对象
const P=p.then{
()=>{
/.......
}
}
这个生成的回调函数的状态,由选定的回调函数的执行结果来确定
以下是四种常见情况
(1)当内部函数正常执行,没有返回值,则生成的promise对象为fulfilled状态.value为undefined
(2)当函数return了非promise的对象,也为fulfilled状态,这个对象即为value值
(3)当函数return了promise对象,则then生成的,就是这个promise对象,状态和value/reason都对的上
(4)当我们手动抛出错误的时候,生成rejected状态,并且reason就是throw的数值
最后补充一种promise的生成方式
const P=new Promise((resolve,reject)=>{
//在函数内部不执行这两个函数
//也就是不变化状态
})
这种生成方式将会生成一个pending状态的promise函数,主要用途是在后面进行promise链条中断
(2)promise生成的时候,对应的函数,数据的关系
const P = new Promise((resolve,reject)=>{
if(符合某种条件){
resolve(data); 1
}else{
reject(err); 2
}
}).then(
(value)=>{}, 3
(reason)=>{} 4
)
这是我们最常用的一种方式,其中1,2函数一旦调用,会把Promise从pending对象转化为对应的状态,并且选择then中的回调函数 . 关于回调函数的选择和状态的改变谁先谁后要视情况而定,不过可以确定的一点是,回调函数的执行一定在状态确认之后
其中data会传入value,err会传入reason.但是13和24并不是同一个函数,仅仅只是参数数值一样
then中的两个参数应该被称作resolved()和rejected()
3.promise常用API
首先Promise构造函数,构造函数中的函数(resolve,reject)=>{}虽然是回调
但是是同步的
(1)resolve函数和reject函数
const a=Promise.resolve();
const b=Promise.reject();
这两个函数前面已经解释过了,这里就不加解释了
(2)util下的promisify函数
有些时候,我们想要把一个函数封装成一个promise的生成对象,比如我们可以手动把fs下的readfile函数封装成一个能返回promise对象的函数,然后再通过这个函数执行获得promise对象,再利用这个对象执行读取结束的异步操作
大致操作就是这样子
function myReadFile(path){
const p=new Promise((resolve, reject)=>{
require("fs").readFile(path,(err, data)=>{
if(err){ reject(err);}
else{ resolve(data); }
})
})
return p;
}
myReadFile("./target").then((value)=>{console.log("结果为"+value)},(reason)=>{console.log("错误"+reason)})
再util中提供了这样一种方法promisify,可以把这类异步回调作为最后一个参数,并且回调函数的风格是先把报错放在第一个参数的样式,给转化为一个能返回promise对象的函数
操作结果如下
var P_fun=util.promisify(require("fs").readFile);
//这个函数的返回值就是一个状态执行结束的promise对象
P_fun("./target").then((value)=>{console.log(value)});
简化很多了对吧,创建出的新的函数中,参数相比于原来,少了最后的那个回调函数
也就是说,这个P_fun函数可以是两个参数(地址,编码)也可以是一个参数(地址)//默认ascii编码
后面结合那两个语法糖,可以节省很多代码
(3)Promise.prototype.then
这是then函数的来源,这里补充点
then中可以只有一个回调函数,只能检测到变为fulfilled状态下的promise对象
p.then((value)=>{ })
then 全称应该是promise.prototype.then(不懂这个请参考js中的原型部分讲解捏)
执行完以后会返回一个新的promise对象
then只有一个参数的时候,默认这个是resolve
另外,一个pormise对象可以通过then绑定多个回调函数
只要符合当前对象状态的回调,都可以执行!
p.then(()=>{})
//第一个会执行
p.then(()=>{})
//第二个也是会执行的
then的返回也是一个promise对象,这个对象的状态由什么决定,上面已经说过了,另外根据其能返回peromise对象这件事,可以最后构成链式promise
(4)Promise.prototype.catch
P_fun("./targ","utf-8").catch((reason)=>{console.log(reason)})
区别就是catch只有一个参数,并且这个回调只能查看错误结果(也就是promise变为rejected)
而且执行完以后也会变成新的promise对象
(5)Promise.prototype.all
传入参数是一个pormise对象数组,如果数组中所有对象均为fulfilled,则新产生的promise对象为成功,value数值为所有对象的value组成的数组
var a =Promise.all([Promise.resolve(1),Promise.resolve(2)]);
否则,对象的状态为失败,reason为第一个失败的promise对象的value
(6)Promise.prototype.race
传入的参数同样是第一个数组,返回的对象是第一个确定状态的promise对象
var b =Promise.race([Promise.resolve(3),Promise.reject(4)]);
经过验证,then/catch函数产生的promise对象的状态
与指定的回调函数的执行结果有关
而与选择的哪个回调函数无关!
4.promise链式调用
链式调用的原理,是then调用以后,会根据选择的回调函数的执行情况来生成一个新的promise
举个例子
const p=new Promise((resolve, reject)=>{
console.log("第一次执行成功");
resolve("hhh");
})
.then((value)=>{console.log("第二次执行成功")})
.then((value)=>{console.log("第三次执行成功")})
.then((value)=>{console.log("第四次执行成功")})
会把上述的东西都输出便,原理就是每次执行完一个promise,会根据这个对象的状态再then中选择一个合适的执行函数进行执行.
(情况1)当没有合适的执行函数的时候,会发生什么?(链式跳转的原理)
还是举个例子先
const p=new Promise((resolve, reject)=>{
resolve("hh");
})
.catch((reason)=>{console.log("执行失败");return Promise.reject("dd")})
.then((value)=>{console.log(value)})
catch不具备识别正确的promise的能力,,并且这里也无法返回正确的理论上来说不会执行?
是会执行的,并且会正确输出 " hh "
执行原理如下:如果then函数前面的promise对象返回的状态,在then或者catch中没有对应的回调函数可以调用,就会自动跳过这个then函数!
按照这个原理,我们常常这样子处理
.then((value)=>{console.log("第二次执行成功")})
.then((value)=>{console.log("第三次执行成功")})//这样的话会截断到这里,然后继续执行
.then((value)=>{console.log("第四次执行成功")})
.catch((reason)=>{console.log("第五次执行失败")})
在最后设置一个catch来截取最终错误的情况,如果某一步产生了错误的结果会直接忽略掉下面的部分,直接运行最后一个catch
还有一种链中断的方法也是应用了这个性质,在执行回调函数的时候返回一个pending状态的promise对象,没有任何函数能接得住这个东西,所以会跳过后面所有的部分.
这里就不演示了
5.async和await语法糖
在2017的新标准中引入了async和await语法糖,这兄弟俩.......
(1)async函数,把一个函数编程异步,并且返回一个promise对象
返回promise的规则和前面的then,catch差不多
(2)await函数,其实不应该叫做函数,是一个操作符
await右侧是一个promise对象 加上这个操作符以后,可以读到promise对象的数值(value或者reason这个参数的数值)
let a=await myReadFile("./target","utf-8");
console.log(a);
输出的是读取结果,也就是data
并且await关键字只能在async标记的函数中实现
async function main(){
let a= await Promise.resolve("ddd");
console.log(a); //ddd
let c=await Promise.reject("ddd").catch((reason)=>{return reason});
console.log(c); //ddd
return 1;
}
const p=main();
console.log(p)
这个东西的执行结果为
(3)简单的实践
结合promisify,await async简化一个读文件和写文件的操作
const util=require("util");
const myreadfile = util.promisify(readfile);
const mywritefile = util.promisify(writefile);
async function main(){
let data=await myreadfile("./target");
let ??=await mywritefile("./目标",data);
}