1.Promise是什么
一直以来,我一直以为Promise就是异步编程,它本身就是一个异步编程的内置模块,直到认真系统学习了它我才认识到
Promise本身并不是异步的,它只是一个容纳异步操作并明确异步状态的容器。
上面的话仅仅是我的个人心得,并不是什么权威的至理名言。
在PromiseA+规范中,Promise具备着三种状态pending,fulfilled和rejected,状态的变化过程只能有两种,一是从pending到fulfilled,二是从pending到rejected。且只能是这两种变化了。
具体如下图所示
也就是说,Promise的原始状态一般为pending,状态一经改变就凝固了。
2.基本用法
首先,明确Promise是ES6才有的,IE不支持ES6,具体的兼容性如下图所示:
语法规范参考MDN
//test.js
const {log}=console
const promise=new Promise(function(resolve, reject){
resolve(2);
})
promise.then((res)=>log(res));
//传入的resolve和reject都是回调函数
Promise一旦创建后就是不可逆的,因为其立即执行了。
就像下面的栗子
let promise=new Promise(function(resolve, reject){
console.log("Promise");
resolve();
})
promise.then(function(){
console.log('resolved.')
})
接下来重在理解Promise其实是一个容器的概念。
直接上一个封装Ajax的栗子
const getJson=function(url){
const promise=new Promise(function(resolve, reject){
const handler=function(){
if(this.readyState!==4){
return;
}
if(this.status===200){
resolve(this.response);
}
else{
reject(new Error(this.statusText));
}
};
const client=new XMLHttpRequest(); //XMLHttpRequest也是浏览器提供的宿主对象
client.open('GET',url);
client.onreadystatechange=handler;
client.responseType='json';
client.setRequestHeader('Accept','application/json');
client.send();
});
return promise;
}
getJson('./post.json').then(function(json){
console.log(json);
},function (error){
console.error('出错了',error);
})
在封装的
getjson函数中,定义了一个Promise,而在Promise中,先忽略handler函数,看到实例化了一个XMLHttpRequest,设置请求的路径,其readyState发现改变执行的函数,请求数据格式,请求头等,然后请求响应。因此,函数
getjson返回的是内部实例化的那个Promise,而Promise被实例化后又实例化了XMLHttpRequest对象,用于发起请求。在这里,如果用过axios并且看过一点源码的话,就会发现大体上都是这样写的。接下来,重点关注
handler函数。可以看到,handler函数是在xhr的readyState改变时触发的,而handler又根据具体状态改变的不同使用resolve和reject改变Promise的状态,进而触发Promise实例化时传入的回调或者是then和catch方法传入的回调(Promise内部有两个数组分别存放这些回调,遵循先进先出的队列原则)。这样一个封装的Promise方式的xhr请求就这样实现了。大体上axios也是这样,只不过其在Node也可以使用,而Node没有XMLHttpRequest而是http模块,这以后再说。总结下,Promise是一个容纳异步操作的容器,当异步操作响应、获得返回结果、再或者是抛出异常时就改变Promise的状态,进而调用传入Promise的回调,Promise严格来说并不是异步的。
3.API介绍
下来来说说Promise都有哪些API
3.1 原型方法then
then方法的参数是可选的两个回调函数,一个resolved,一个rejected。看名字就是当promise实例的状态从pending变为fulfilled和rejected调用的回调。但是呢,then可以返回一个新的Promise实例,进而实现链式调用,有Jquery的味道了。因此,then返回的Promise实例p1是受到前面一个Promise实例影响的,前面的改变其才能改变。
/**
* @description 测试then的链式调用
* @date 2021/8/28
* @author ZeroTower
* @website https://www.zerotower.cn
**/
const p1=new Promise(function(resolve,reject){
resolve('success');
})
p1.then(()=>{console.log("first",1)},()=>{console.log("first failed")})
.then(()=>{console.log("second",2)},()=>{console.log("second failed")})
.then(()=>{console.log("third",3)},()=>{console.log("third failed")})
/**
*
first 1
second 2
third 3
*/
//如果p1的状态变为rejected,后续的就会调用rejected回调
const p2=new Promise(function(resolve,reject){
reject("failed")
})
p2.then(()=>{console.log("first",1)},()=>{console.log("first failed")})
.then(()=>{console.log("second",2)},()=>{console.log("second failed")}) //但是这句打印了second 2
.then(()=>{console.log("third",3)},()=>{console.log("third failed")}) //但是这句打印了third 3
3.2 原型方法catch
catch方法其实就是then(null,rejected)或者then(undefined,rejected)的别名,也就是发生错误时的回调.
3.3 原型方法finally
finally方法更是简单,是只要Promise实例的状态改变(当然,原有的状态总是pending)它传入的回调就被调用。不管resolved或者rejected。其实,可以考虑到执行
then方法后,无论是resolved回调还是rejected回调,总要再执行同样的一个方法,于是,Promise.prototype.finally可以重写为 以下
Promise.prototype.finally=function(callback){
let P=this.constructor;
return this.then(
value=>P.resolve(callback()).then(()=>value),
reason => P.resolve(callback()).then(()=>{throw reason})
);
};
因此,finally方法实际上是then的特例。
3.4 静态方法all
Promise.all()方法接受一个数组对象,数组对象中每个都是Promise实例,如果不是将调用Promise.resolve()转为Promise实例再进一步处理。说白了,就是把多个Promise直接封装为一个Promise。这就涉及到一个问题,传入
Promise实例的状态对传入Promise实例状态的影响。用代码来分析吧。
代码很长,你们忍一下。
const getJson=function(url){ const promise=new Promise(function(resolve, reject){ const handler=function(){ if(this.readyState!==4){ return; } if(this.status===200){ resolve(this.response); } else{ reject(new Error(this.statusText)); } }; const client=new XMLHttpRequest(); //XMLHttpRequest也是浏览器提供的宿主对象 client.open('GET',url); client.onreadystatechange=handler; client.responseType='json'; client.setRequestHeader('Accept','application/json'); client.send(); }); return promise; } const promises=[1,3,5,7,9].map(function(id){ return getJson(`./${id}.json`) }) Promise.all(promises).then(()=>{}) .catch(()=>{})
上面的代码,传入了5个Promise,在all()方法里,只有这五个
Promise实例都变为resolved,返回的实例才能变为resolved,而返回value是一个数组,每个子value是传入的Promise实例返回的value。
而一旦传入的Promise实例有一个状态变为rejected,新的实例状态马上就为rejected,且传入的
Promise实例首先变为rejected的,其reason作为新实例rejected回调中的reason。
3.5 Promise.race
从单词race本意的赛跑含义就可以知道,同样传入promise的实例数组,决定新实例的状态肯定是最先改变的那一个,新实例的value和reason的值也是最先改变实例的value和reason。
3.6 Promise.allSettled()
又从单词表面理解,'解决了所有'。应该是所有的Promise实例状态变化了干些什么,注意,是所有的!!不管是不是resolved!!
因此,返回的实例的状态总是resolved!!
3.7Promise.any()
还是从单词的语义“任何”来理解,任何一个传入的Promise实例变为resolved,新的Promise实例的状态就是resolved,因此,当传入的Promise实例都变为rejected,新的Promise实例才能变为rejected。
3.8Promise.resolve()
这个方法负责把所有的对象转为新的promise。
3.8.1 非Promise实例转为Promise
把不是Promise的对象转化为Promise,然后可以通过then()方法输入这个对象。
const o1={ name:'zerotower', age:22 } const p1=Promise.resolve(o1); p1.then(value=>console.log(value.toString())) /* *{ name:'zerotower', age:22 } */
3.8.2 如果传入的就是promise返回也是promise
也就是说,传入一个Promise实例,返回的也就是这个Promise实例。
3.8.3 如果传入的对象具有then方法,就会立刻执行这个方法
如果一个对象具有then方法,那么该对象叫做
thenable对象,使用Promise.resolve()方法时,这个then方法会被立刻执行。let o2={ name: "zerotower", age:22, then:function(){ console.log("My age is: " +this.age); }, printName:function(){ console.log("My name is:"+this.name) } } const p2=Promise.resolve(o2); //o2的then方法被立刻执行了 console.log(p2);//Promise { <pending> } p2.then((value)=>console.log(value)) //啥也没有
3.8.4 如果不给参数的话,将直接创建resolved的实例
不给定参数,直接返回一个状态为resolved的
Promise实例,且直接返回的这个实例,在下一次任务循环前执行,因为属于宏任务,执行的时机高于微任务(setTimeout,setInterval等)。setTimeout(()=>console.log(1)); new Promise((resolve,reject)=>{ resolve(2); }).then((result)=>console.log(result)); //输出顺序是 //2 //1
3.9 Promise.reject()
和
Promise.resolve()差不多,就是返回的Promise实例状态是rejected。而且给定的参数原封不动地作为reason。const p1=Promise.reject("出错了"); console.log(p1); p1.then((result)=>console.log(result)) .catch(err=>console.log("error",err));
4.总结
关于
Promise我就先说到这里,对于我个人来说,Promise实际是个容器。而且Promise的方法大多都会返回一个新的Promise实例,要记住所有的静态方法(原型方法记不住自杀)其实就是以resolved状态为观测基础,根据方法名的含义简单记一下。
方法名 含义 返回实例状态条件 value reason 备注 all 所有 返回resolved需要所有都为resolved 所有的 第一个rejected的 race 竞争 有一个状态改变其就能决定返回的状态 第一个改变的 第一个改变的 allSettled 一切安顿 所有的状态决定返回的状态,返回的总是resolved 所有的value ---------- any 任何 任何一个resolved--->resolved 所有的value resolve 解决 resolved rejected 拒绝 rejected 传入的参数 由于写文章也挺累的,加上个人水平不高,写出来很多地方可能不是很容易理解,欢迎补充提出宝贵的意见。
这部分的操作代码在github