浅谈Promise和其API

268 阅读7分钟

1.Promise是什么

一直以来,我一直以为Promise就是异步编程,它本身就是一个异步编程的内置模块,直到认真系统学习了它我才认识到

Promise本身并不是异步的,它只是一个容纳异步操作并明确异步状态的容器

上面的话仅仅是我的个人心得,并不是什么权威的至理名言。

PromiseA+规范中,Promise具备着三种状态pending,fulfilledrejected,状态的变化过程只能有两种,一是从pending到fulfilled,二是从pending到rejected。且只能是这两种变化了。

具体如下图所示

也就是说,Promise的原始状态一般为pending,状态一经改变就凝固了。

2.基本用法

首先,明确Promise是ES6才有的,IE不支持ES6,具体的兼容性如下图所示:

image-20210828064512441

语法规范参考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实例化时传入的回调或者是thencatch方法传入的回调(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状态为观测基础,根据方法名的含义简单记一下。

方法名含义返回实例状态条件valuereason备注
all所有返回resolved需要所有都为resolved所有的第一个rejected的
race竞争有一个状态改变其就能决定返回的状态第一个改变的第一个改变的
allSettled一切安顿所有的状态决定返回的状态,返回的总是resolved所有的value----------
any任何任何一个resolved--->resolved所有的value
resolve解决resolved
rejected拒绝rejected传入的参数

由于写文章也挺累的,加上个人水平不高,写出来很多地方可能不是很容易理解,欢迎补充提出宝贵的意见。

这部分的操作代码在github