Promise:基本操作、常见API、使用方法、手撕Promise、async/await


Promise指定回调函数的方式更加灵活,且支持链式调用,可以解决回调地狱的问题。

回调地狱:回调函数嵌套调用,外部回调执行结果是内部函数执行的条件,不便于阅读且不便于异常处理,解决方式就是promise(或async/await)

一、Promise的常见操作
1.初体验

先看一个抽奖案例

// 生成随机数
function rand(m,n) {
    return Math.ceil(Math.random() * (n-m+1) + m-1);
}

const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        let n = rand(1, 100);
        if (n <= 30) {
            resolve(n); //把Promise对象的状态从未完成=>成功
        } else {
            reject(n);  //把Promise对象的状态从未完成=>失败
        }
    }, 1000);
});

Promise接收两个参数,分别是resolve和reject,这两个参数都是函数类型
我们可以用.then来指定成功或失败的回调。(第一个回调是成功的回调,参数为调用resolve时传过来的参数,第二个回调是失败的回调,参数为调用reject传过来的参数)

p.then((res)=>{
    console.log('异步成功:',res);  //异步成功:12
},(error)=> {
    console.log('异步失败:',error);  //异步失败:66
})

下面这个用catch写法和上面是等价的:

p.then((res)=>{
    console.log('异步成功:',res); //异步成功:12
}).catch((error)=> {
    console.log('异步失败:',error);  //异步失败:66
})
2.使用Promise封装原生AJAX
//定义一个方法,发送ajax请求
function sendAjax() {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.responseType = 'json';  //指定返回结果格式
        xhr.open('GET', 'https://ku.qingnian8.com/dataApi/news/navlist.php');
        xhr.send();
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr.status);
                }
            }
        }
    });
}

//调用方法返回的是promise对象,直接.then就欧了
sendAjax().then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})
3.Promise实例对象的两个属性

回到前边的抽奖案例,如果我们打印一下p,可以看到两个属性:
在这里插入图片描述
上面是成功,下面是失败
在这里插入图片描述
是的,这两个属性分别是状态属性PromiseState和结果值属性PromiseResult

(1)状态属性PromiseState
有三个值,分别是pending、fulfilled、rejected。
promise状态的改变只有两种,分别是:(初始化,状态:pending)

  • 当调用resolve(成功),状态:pending => fulfilled
  • 当调用reject(失败),状态:pending => rejected

而且一个promise对象只会改变一次,改变之后就不会再变

(2)结果值属性PromiseResult
这个属性保存的是异步成功或失败的结果
无论成功还是失败,都只有一个结果数据
这个结果是通过resolve和reject这两个函数参数,保存在PromiseResult属性中的
成功穿过去的参数一般叫value,失败的参数一般叫reason(当然我习惯写res和err)

4.Promise的工作流程

在这里插入图片描述

5.Promise的API

Promise对象中的函数是同步调用的,下面的代码依次输出:我同步执行,奥里给

const p = new Promise((resolve, reject) => {
	//这个函数是同步调用的
	console.log('我同步执行');
});
console.log('奥里给');
(1).then和.catch

这部分上面讲过了

(2)Promise.resolve(参数)

这个方法可以返回一个Promise对象,注意:

1、如果传入的参数是一个非Promise类型,那么返回一个成功的Promise对象,参数也就是成功的回调中的value值

const p1 = Promise.resolve(521);
console.log(p1);

在这里插入图片描述

2、如果传入的参数是一个Promise类型,那么返回的结果取决于传入的Promise的结果
如果结果为resolve,那么返回成功的Promise,值就是里面调用resolve的参数成功:

const p2 = Promise.resolve(new Promise((resolve,reject) => {
    resolve('成功');
}))
console.log(p2);

在这里插入图片描述

3、如果结果为reject,那么返回失败的Promise,值就是里面调用reject的参数失败:

const p2 = Promise.resolve(new Promise((resolve,reject) => {
    // resolve('成功');
    reject('失败');
}))
console.log(p2);

在这里插入图片描述
不想让控制台报错,只需要用catch捕获一下:

const p2 = Promise.resolve(new Promise((resolve,reject) => {
    // resolve('成功');
    reject('失败');
}))
console.log(p2);
p2.catch(err => console.log(err));

在这里插入图片描述

(3)Promise.reject(参数)

不管传入的参数是什么,都会返回一个失败的Promise对象,传入的参数是什么,结果就是什么(若参数是Promise对象,那么结果值也是Promise对象)。

1、如果传的是非Promise,返回的是失败的Promise,结果就是传的值:

const p3 = Promise.reject(520);
console.log(p3);

在这里插入图片描述

2、如果传的是成功的Promise,返回的是失败的Promise对象,该对象的结果值PromiseResult是成功的Promise对象:

const p4 = Promise.reject(new Promise((resolve,reject)=>{
    resolve('OK');
    // reject('错误');
}))
console.log(p4);

在这里插入图片描述

3、如果传的是失败的Promise,返回的是失败的Promise对象,该对象的结果值PromiseResult是失败的Promise对象:

const p4 = Promise.reject(new Promise((resolve,reject)=>{
    // resolve('OK');
    reject('错误');
}))
console.log(p4);

在这里插入图片描述

(4)Promise.all([参数1,参数2…])

1、返回结果是一个Promise对象,该方法参数是一个Promise对象构成的数组,只有所有的Promise成功,返回的状态才是成功,值为一个成功结果值构成的数组

const p1 = new Promise((resolve,reject)=> {
    resolve('OK');
})
const p2 = Promise.resolve('成功');
const p3 = Promise.resolve('欧了');
const result = Promise.all([p1,p2,p3]);
console.log(result);

在这里插入图片描述

2、如果有一个失败,那么返回的Promise状态为失败,结果值为第一个失败的结果值

const p1 = new Promise((resolve,reject)=> {
    resolve('OK');
})
const p2 = Promise.reject('失败');
const p3 = Promise.reject('完蛋');
const result = Promise.all([p1,p2,p3]);
console.log(result);

在这里插入图片描述
这玩意儿应用场景还是蛮多的,比如同时去改数据库的两个表,两个表都改完了,再使用.then的回调提示用户修改成功。

(5)Promise.race([参数1,参数2…])

参数是多个Promise对象构成的数组,这个race本身是赛跑的意思,所以这个API的作用就是:多个异步操作,哪个先返回结果(先完成),那么调用这个API返回的就是谁。

比如下面这段代码,p1加了定时器,那么p2先返回结果,所以result返回的就是一个失败的Promise,值是p2的结果值

const p1 = new Promise((resolve,reject)=> {
    setTimeout(() => {
        resolve('OK');
    }, 1000);
})
const p2 = Promise.reject('失败');
const p3 = Promise.reject('完蛋');
const result = Promise.race([p1,p2,p3]);
console.log(result);

在这里插入图片描述

二、一些关键问题
1.改变对象状态的方式

有三种方式可以改变Promise对象的状态,分别是(1)resolve函数 (2)reject函数 (3)抛出错误

const p = new Promise((resolve,reject)=> {
    //1.resolve函数
    resolve('成功'); //pending => resolved/fulfilled
    //2.reject函数
    reject('失败'); //pending => rejected
    //3.抛出错误
    throw '出问题了!';  //pending => rejected
})
console.log(p);
2.能否执行多个回调

只要Promise对象的状态改变,那么对应的回调不管有几个,都会执行

const p = new Promise((resolve,reject)=> {
    resolve('DJ');
})

//只要p状态改变为resolved,下面三个回调都会执行
p.then(res=>console.log(res));
p.then(res=>alert(res));
p.then(res=>console.log(res,'drop the beat'));
3.改变状态和指定回调的顺序

有两种情况:

1、若状态改变为同步,那么就是改变状态 => 指定回调 => 执行回调

const p = new Promise((resolve,reject)=> {
     resolve('DJ');
})
p.then(res=>console.log(res));

2、若状态改变为异步,那么就是指定回调 => 改变状态 => 执行回调

const p = new Promise((resolve,reject)=> {
    setTimeout(()=>{
        resolve('DJ');
    },1000)
})
p.then(res=>console.log(res));

记住,执行回调永远在状态改变之后,执行回调和指定回调不是一个概念。指定回调是执行then方法而不是执行then里的嘎达们

4.then/catch方法返回结果由什么决定

调用then(或catch)方法返回的还是Promise对象,返回的这个Promise的状态和结果取决于then中回调的返回值。

1、如果返回非Promise,那么结果为成功的Promise对象,值就是返回值。(这里如果不写返回值,返回的也是成功的Promise,因为不写return,返回的是undefined,undefined也是非Promise类型的数据)
2、抛出错误,那么结果为失败的Promise对象,值就是抛出的值
3、返回一个Promise对象,那么结果取决于该Promise的状态

不管p的状态是成功还是失败,后面链式调用then(或catch)返回的结果,都取决于它里面的回调,就是上面那三个情况

const p = new Promise((resolve,reject)=> {
    resolve('成功');
})
const result = p.then(res => {
    //1.返回非Promise,那么结果为成功的Promise对象,值就是返回值
    //(不写return返回undefined)
    return 521; //result结果为fulfilled、521
    //2.抛出错误,那么结果为失败的Promise对象,值就是抛出的值
    throw '出了问题';  //result结果为rejected、出了问题
    //3.返回一个Promise,那么结果取决于该Promise的状态和值
    return new Promise((resolve,reject)=> {
        // resolve('OK');
        reject('ERROR');
    })
}, error => {
    // return 521;   //返回成功的Promise
    // throw '出了问题';  //返回失败的Promise
    return new Promise((resolve,reject)=> {
        resolve('OK');  //返回成功的Promise,值为OK
        // reject('ERROR');
    })
})
console.log(result);  //上面代码没注释,这里的结果应根据返回值决定

catch也是一样的规则。

const p = new Promise((resolve,reject)=> {
    resolve('成功');
})
const result = p.catch(err => {
    //return 521;  //返回成功的Promise,值为521
    //throw '出了问题';  //result结果为rejected、出了问题
    return new Promise((resolve,reject)=> {
        // resolve('OK');
        reject('ERROR');
    })
})
console.log(result); //返回失败的Promise,值为ERROR
5.串联多个任务(链式调用)

由于.then/.catch返回的还是Promise,所以可以链式调用,解决回调地狱问题。下一个.then/.catch中的回调的参数res是上一个then的Promise结果值(如果上一个没写返回值,那么上一个返回成功的Promise,值为undefined)

const p = new Promise((resolve, reject) => {
    resolve('ok');
})

p.then(res => {
    return new Promise((resolve, reject) => {
        resolve('success');
    })
}).then(res => {
    console.log(res); //success
}).then(res => {
    console.log(res); //undefined
})
6.异常穿透

链式调用,在最后写个.catch(或者.then第一个回调写空,第二个回调捕获错误)可以捕获到前面的第一个错误,如果.then中途出现错误,则依次向下执行,寻找处理错误的回调,直到找到.catch(或某个.then的第二个回调),然后执行该回调。

const p = new Promise((resolve,reject) => {
    resolve('ok');
    // reject('错误!');
})

p.then(res => {
    console.log('111');
}).then(res => {
    console.log('222');
    throw '嗷嗷嗷错误'
}).then(res => {
    console.log('333');
}).catch(err => {
    console.warn(err); //嗷嗷嗷错误
})
7.中断Promise链条

其实我们在.then时,不管上一个Promise结果是什么,所有.then都会依次执行,因为每个.then都有两个回调(第二个回调是捕获错误的回调),比如下面的代码,输出结果是:111,dj,333,第二个.then执行了捕获错误的回调,由于没有返回值,返回一个成功的Promise,值为undefined,所以后面的.then还会继续调用

const p = new Promise((resolve, reject) => {
    resolve('ok');
})

p.then(res => {
    console.log('111');
    throw 'dj';
}).then(res => console.log('222'), err => console.log(err))
    .then(res => console.log('333'), err => console.log(err))

想要中断.then和最后.catch的调用,有且只有一种方式:

返回一个pending状态的Promise

const p = new Promise((resolve, reject) => {
    resolve('ok');
})
p.then(res => {
    console.log('111');
    // throw 'dj';
    return new Promise((resolve,reject)=>{}); //pending
}).then(res => console.log('222'), err => console.log(err))
    .then(res => console.log('333'), err => console.log(err))

上面程序执行结果为:111

三、手撕Promise
1.搭建整体结构

先正常搭一个Promise的使用,只不过这个Promise是从我们自定义的文件中引入的构造函数(或者类,这里用ES5的构造函数写吧)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手撕Promise</title>
    <script src="./Promise.js"></script>
</head>
<body>
    <script>
        const p = new Promise((resolve,reject)=>{
            resolve('ok');
        })
        p.then(res => {
            console.log(res);
        }, err => {
            console.log(err);
        })
    </script>
</body>
</html>

Promise.js

function Promise(executer) {

}
//1.then方法的封装
Promise.prototype.then = function(onResolved,onRejected) {

}
2.resolve和reject的实现

这里的参数executer是一个函数,且是立即执行的,executer的参数是两个函数,分别是成功和失败的函数,即我们常写的resolve和reject。这两个函数调用做的事情一样:1、改变对象的状态,2、改变对象的结果值

function Promise(executer) {
    this.PromiseState = 'pending';
    this.PromiseResult = undefined;
    //参数executer是一个立即调用的函数
    //且该函数还接收两个参数,分别是两个函数
    const success = (data) => {
        //这是resolve对应的回调
        //1.改变对象的状态:pending=>resolved
        this.PromiseState = 'resolved';
        //2.改变对象的结果值
        this.PromiseResult = data;
    }
    const fail = (data) => {
        //这是reject对应的回调
        //1.改变对象的状态:pending=>resolved
        this.PromiseState = 'rejected';
        //2.改变对象的结果值
        this.PromiseResult = data;
    }
    executer(success, fail);
}
//1.then方法的封装
Promise.prototype.then = function (onResolved, onRejected) {

}
3.throw抛出错误改变状态

如果我们要抛出错误:

const p = new Promise((resolve,reject)=>{
    throw 'error';
})

那么应该在Promise构造函数的立即执行函数executer外层包一个try…catch

try {
    executer(success, fail);
} catch(data) {
    //如果抛出错误,那么就执行下面的代码
    fail(data);
}

这样就可以捕获异常,并改变Promise对象的状态

4.状态一旦改变就不能再变

只需要在成功和失败的回调中加个判断,如果当前状态不是pending,说明状态已经改变了,就不再执行后面的代码。

if(this.PromiseState !== 'pending') return;
5.then方法的初步封装
p.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

因为then方法有两个回调,成功执行第一个,失败执行第二个,所以只需要加个判断就行了,调用函数传的参数就是Promise构造函数中的结果值属性

Promise.prototype.then = function (onResolved, onRejected) {
    //判断走哪个回调
    if(this.PromiseState === 'resolved') {
        onResolved(this.PromiseResult);
    }
    if(this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
}
6.异步任务回调的执行

如果是异步任务,那么根据js代码同步执行特性,会先调用then方法,再改变状态,我们要做到在改变状态后再执行then方法中的回调,怎么办?

const p = new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve('ok');
    }, 1000);
})

p.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

如果先调用then方法,那么Promise的状态的pending,所以要加个判断,当状态为pending时,就把两个回调保存到Promise实例的某个属性上

function Promise(executer) {
    this.PromiseState = 'pending';//默认应该是等待
    this.PromiseResult = undefined;
    //定义一个属性来存放then函数的回调
    this.callback = {};
    ......
}

Promise.prototype.then = function (onResolved, onRejected) {
    //判断同步任务下走哪个回调
    if (this.PromiseState === 'resolved') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    //如果是异步任务(先指定回调再改变状态再执行回调)
    if (this.PromiseState === 'pending') {
        //把回调存到该实例的属性上
        this.callback.onResolved = onResolved;
        this.callback.onRejected = onRejected;
    }
}

这样我们就可以实现,异步任务resolve/reject函数调用导致状态改变后,再执行对应的回调

function Promise(executer) {
	......
	
    const success = (data) => {
        //这是resolve对应的回调
        //状态改变后就不能再变,加个判断
        if (this.PromiseState !== 'pending') return;
        //1.改变对象的状态:pending=>resolved
        this.PromiseState = 'resolved';
        //2.改变对象的结果值
        this.PromiseResult = data;
        //3.如果是异步,要在以上步骤结束后,执行对应的回调
        if(this.callback.onResolved){
            this.callback.onResolved(data);
        }
    }
    const fail = (data) => {
        //这是reject对应的回调
        //状态改变后就不能再变,加个判断
        if (this.PromiseState !== 'pending') return;
        //1.改变对象的状态:pending=>resolved
        this.PromiseState = 'rejected';
        //2.改变对象的结果值
        this.PromiseResult = data;
        //3.如果是异步,要在以上步骤结束后,执行对应的回调
        if(this.callback.onRejected) {
            this.callback.onRejected(data);
        }
    }
	......
}
7.执行多个then的回调

异步任务Promise如果有多个then的回调,那么原Promise中是全部都要执行的,但是我们自己封装的这个,会执行最后一个,原因在于我们调用then方法是往对象中保存回调,这样的话后面then方法的回调会覆盖前面的回调。

const p = new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve('ok');
    }, 1000);
    // reject('notok');
    // throw 'error';
})

p.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

p.then(res=>{
    alert(res);
}, err => {
    alert(err);
})

所以这时我们应该修改一下数据结构,把保存到实例上的属性变成数组,每次调用then方法,就把成功和失败的回调以对象的形式push进去

function Promise(executer) {
    ......
    //定义一个属性来存放then函数的回调们
    this.callbacks = [];
}

Promise.prototype.then = function (onResolved, onRejected) {
    ......
    //如果是异步任务(先指定回调再改变状态再执行回调)
    if (this.PromiseState === 'pending') {
        //把回调存到该实例的属性上
        // this.callback.onResolved = onResolved;
        // this.callback.onRejected = onRejected;
        this.callbacks.push({
            onResolved: onResolved,
            onRejected   //简写
        })
    }
}

然后再遍历调用

function Promise(executer) {
    ......
    const success = (data) => {
        //这是resolve对应的回调
        ......
        //3.如果是异步,要在以上步骤结束后,执行对应的回调
        // if(this.callback.onResolved){
            // this.callback.onResolved(data);
        // }
        if(this.callbacks.length != 0) {
            this.callbacks.forEach(item => {
                item.onResolved(data);
            })
        }
    }
    const fail = (data) => {
        //这是reject对应的回调
        ......
        //3.如果是异步,要在以上步骤结束后,执行对应的回调
        // if(this.callback.onRejected) {
            // this.callback.onRejected(data);
        // }
        if(this.callbacks.length != 0) {
            this.callbacks.forEach(item => {
                item.onRejected(data);
            })
        }
    }
    ......
}
8.同步任务then方法的返回结果

我们知道,执行then方法,返回的还是Promise,且返回Promise的状态是then中回调决定的,具体去看第二章第四节。所以,我们应该对then方法来一些小小的改写

首先包个返回Promise,Promise的回调是同步执行的

Promise.prototype.then = function (onResolved, onRejected) {
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        //判断同步任务下走哪个回调
        if (this.PromiseState === 'resolved') {
           ......
        }
        if (this.PromiseState === 'rejected') {
            .......
        }
        //如果是异步任务(先指定回调再改变状态再执行回调)
        if (this.PromiseState === 'pending') {
          ......
        }
    })
}

既然then的返回Promise状态和结果是由回调决定的,那我们就要拿到回调的返回值,然后判断返回值是否是Promise类型的数据。

1、非Promise那么就返回成功的Promise,结果值就是回调的返回值。
2、如果是Promise类型的话,那么一定有then方法,成功会走第一个,失败会走第二个。

其实总体来说,要抓住一个要点,就是调用then方法返回的Promise的状态如果要改变,需要调用resolve和reject函数,而这两个函数的调用时机和回调息息相关。

Promise.prototype.then = function (onResolved, onRejected) {
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        //判断同步任务下走哪个回调
        if (this.PromiseState === 'resolved') {
           const outcome = onResolved(this.PromiseResult);
            //判断回调返回结果是否是Promise类型
            if (outcome instanceof Promise) {
                //Promise类型的数据,返回状态要和它一致
                outcome.then(res => {
                    //如果是成功的Promise,那一定会走这个回调
                    resolve(res); //变成成功的Promise,值为返回Promise的结果值
                }, err => {
                    //如果是失败的Promise,那一定会走这个回调
                    reject(err); //变成失败的Promise,值为返回Promise的结果值
                })
            } else {
                //非Promise类型,返回成功的Promise
                resolve(outcome);
            }
            //如果抛出错误,不用再另外写try-catch,封装时写过了
            //所以任何Promise实例执行器函数出现错误,都可以直接捕获
        }
        if (this.PromiseState === 'rejected') {
            const outcome = onRejectd(this.PromiseResult);
            //判断回调返回结果是否是Promise类型
            if (outcome instanceof Promise) {
                //Promise类型的数据,返回状态要和它一致
                outcome.then(res => {
                    //如果是成功的Promise,那一定会走这个回调
                    resolve(res);
                }, err => {
                    //如果是失败的Promise,那一定会走这个回调
                    reject(err);
                })
            } else {
                //非Promise类型,返回成功的Promise
                resolve(outcome);
            }
            //如果抛出错误,不用再另外写try-catch,封装时写过了
            //所以任何Promise实例执行器函数出现错误,都可以直接捕获
        }
        //如果是异步任务(先指定回调再改变状态再执行回调)
        if (this.PromiseState === 'pending') {
          ......
        }
    })
}
9.异步任务then方法的返回结果

这里仍然是保存回调到Promise对象的属性上,不同的是我们不只是保存成功和失败的回调,而是保存更多东西,那就是对返回的Promise的状态的改变操作resolve/reject。

1、套个箭头函数保存到属性上,当异步结束后会在状态改变后执行这个箭头函数
2、首先要执行then中的回调,并拿到返回值,进而判断是否是Promise类型的数据
3、非Promise就调用resolve,是Promise再…和上面同步是一样的操作
4、不同的是,在异步中需要加try-catch,这是因为这个函数的执行不在Promise的主函数中(在改变状态的函数中),所以异常无法捕获,需要我们手动捕获

Promise.prototype.then = function (onResolved, onRejected) {
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        //判断同步任务下走哪个回调
        ......
        //如果是异步任务(先指定回调再改变状态再执行回调)
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: () => {
                    try {
                        const overcome = onResolved(this.PromiseResult);
                        if (overcome instanceof Promise) {
                            overcome.then(res => {
                                resolve(res);
                            }, err => {
                                reject(err);
                            })
                        } else {
                            reject(overcome);
                        }
                    } catch (e) {
                        reject(e);
                    }
                },
                onRejected: () => {
                    try {
                        const overcome = onRejected(this.PromiseResult);
                        if (overcome instanceof Promise) {
                            overcome.then(res => {
                                resolve(res);
                            }, err => {
                                reject(err);
                            })
                        } else {
                            reject(overcome);
                        }
                    } catch (e) {
                        reject(e);
                    }
                }
            })
        }
    })
}

说实话,这里其实比较懵,懵的点在于,把箭头函数保存到属性上,然后去调用,这样里面的resolve,reject还能更改then方法中返回的Promise的状态?很奇怪…

10.then方法中封装重复代码

最好写箭头函数,不然this指向会出现问题

Promise.prototype.then = function (onResolved, onRejected) {
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        //公共的改变返回Promise状态的方法封装起来
        let changeState = (name) => {
            try {
                const outcome = name(this.PromiseResult);
                //判断回调返回结果是否是Promise类型
                if (outcome instanceof Promise) {
                    //Promise类型的数据,返回状态要和它一致
                    outcome.then(res => {
                        //如果是成功的Promise,那一定会走这个回调
                        resolve(res);
                    }, err => {
                        //如果是失败的Promise,那一定会走这个回调
                        reject(err);
                    })
                } else {
                    //非Promise类型,返回成功的Promise
                    resolve(outcome);
                }
            } catch (e) {
                reject(e);
            }
        }
        //判断同步任务下走哪个回调
        if (this.PromiseState === 'resolved') {
            changeState(onResolved);
            //如果抛出错误,不用再另外写try-catch,封装时写过了
            //所以任何Promise实例执行器函数出现错误,都可以直接捕获
        }
        if (this.PromiseState === 'rejected') {
            changeState(onRejected);
        }
        //如果是异步任务(先指定回调再改变状态再执行回调)
        if (this.PromiseState === 'pending') {
            //把回调存到该实例的属性上
            // this.callback.onResolved = onResolved;
            // this.callback.onRejected = onRejected;
            this.callbacks.push({
                onResolved: () => {
                    changeState(onResolved);
                },
                onRejected: () => {
                    changeState(onRejected);
                }
            })
        }
    })
}
11.catch方法封装,异常穿透
const p = new Promise((resolve,reject)=>{
     reject('ok');
})
p.then(res => {
    console.log(111);
}).then(res => {
    console.log(222);
}).then(res => {
    console.log(333);
}).catch(err => {
    console.log(err)
})

catch方法逻辑和then差不多,所以可以直接调用then,返回Promise对象。不同的是catch只接收错误的回调,所以成功的回调这里传个undefined

Promise.prototype.catch = function(onRejected) {
    return this.then(undefined, onRejected);
}

但是只有这样的话,上面的链式调用会报错,因为then中都没有指定第二个错误回调,所以第二个回调是undefined,如果状态是reject,那么怎么去调一个undefined?所以我们要在then方法中指定一个错误回调的默认值,这个默认值要抛出错误,从而继续往后面查找错误的回调,直到找到catch方法,这样就实现了异常穿透

Promise.prototype.then = function (onResolved, onRejected) {
    //判断第二个回调是否存在,没有就写个默认的
    if(typeof onRejected !== 'function') {
        onRejected = err => {
        	console.log('第二个默认回调执行');
            throw err;//一直往后找错误的回调
        }
    }
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        ......
    })
}

同样的,如果不写第一个回调,我们也要实现值的传递

Promise.prototype.then = function (onResolved, onRejected) {
    //判断第二个回调是否存在,没有就写个默认的
    if(typeof onRejected !== 'function') {
        onRejected = err => {
        	console.log('第二个默认回调执行');
            throw err;//一直往后找错误的回调
        }
    }
    //如果不写第一个回调,也能实现值的传递
    if(typeof onResolved !== 'function') {
        onResolved = res => {
        	console.log('第一个默认回调执行');
        	return res
        };
    }
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        ......
    })
}
12.Promise.resolve方法封装

由于这个方法是在Promise上的方法,不是实例上的,所以不用写在原型上

const p = Promise.resolve(521);
console.log(p)
const p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('ok');
}))
console.log(p2)
//3.resolve方法封装
Promise.resolve = function(data) {
    return new Promise((resolve, reject) => {
        if(data instanceof Promise) {
            data.then(res => {
                resolve(res);
            }, err => {
                reject(err);
            })
        }else {
            resolve(data);
        }
    })
}
13.Promise.reject方法封装

不管参数是什么,都返回失败的Promise。这个比较简单,就不多说了

//4.reject方法封装
Promise.reject = function(data) {
    return new Promise((resolve, reject) => {
        reject(data);
    })
}
14.Promise.all方法封装
const p1 = Promise.resolve(521);
const p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('err');
}))
const p3 = Promise.resolve(666)
console.log(Promise.all([p1,p2,p3]));

all方法的原则就是,传入一个由Promise组成的数组,返回一个Promise,只有全部Promise都成功,才返回成功的Promise,值为成功结果值组成的数组,只要有一个失败就返回失败的Promise,值为失败的结果值。

所以,我们可以使用for循环去检索每一个Promise对象,既然是Promise,就一定有then方法,成功走第一个回调,失败走第二个回调。

1、设置一个flag,用来记录是否有失败的Promise,如果有就置为false
2、设置一个数组arr,用来存储成功的结果值
3、for循环结束后进行判断,如果flag为true,说明所有的Promise都是成功的。
4、注意,状态改变后就不能再变噢

//5.all方法封装
Promise.all = function(promiseArr) {
    return new Promise((resolve, reject) => {
        let flag = true; //判断是否所有都成功
        let arr = []; //存储成功的结果
        for(let i = 0; i < promiseArr.length; i++) {
            promiseArr[i].then(res => {
                // arr.push(res); //这么写可能会由于异步出现顺序问题
                arr[i] = res;  //这么写没事,是let的块级作用域效果
            }, err => {
                reject(err);
                flag = false;
            })
        }
        //循环结束后,判断是否所有Promise都是成功的
        if(flag) {
            resolve(arr);
        }
    })
}

这里有一个尤其要注意的点,就是保存成功结果值到arr时,要使用arr[i] = res;,不使用arr.push(res);,因为如果使用push,当传入的Promise中有异步任务时,调用回调的时机会不同,这样的话push的顺序可能就会发生变化,和传入的Promise顺序不同。

而使用arr[i] = res;,正好借助了let块级作用域的特点,解决了这个问题。这是借助了下标i去规定它们的顺序,let声明的循环每一轮都会保存相应的i值,不管谁先执行完,顺序都是跟i保持一致的。

比如下面这个例子,如果是push,那么后面两个会先push;如果是arr[i] =res,也是后面两个先添加,第一个最后添加,但是顺序不变。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
    	resolve('err');
    },1000);
})
const p2 = Promise.resolve(521);
const p3 = Promise.resolve(666)
console.log(Promise.all([p1,p2,p3]));
15.Promise.race方法封装

这个就比较简单了,哪个先改变状态,哪个就决定返回的结果和状态。

//6.race方法封装
Promise.race = function(promiseArr) {
    return new Promise((resolve, reject) => {
        for(let i = 0; i < promiseArr.length; i++) {
            promiseArr[i].then(res => {
                resolve(res);
            }, err => {
                reject(err);
            })
        }
    })
}
16.细节:then方法的回调是异步执行的
const p = new Promise((resolve, reject) => {
    resolve('ok');
    console.log(111);
})
p.then(res => {
    console.log(222);
})
console.log(333);  
执行顺序应是:111333222

在执行函数的地方包个定时器就行,但这里只是表现形式一样,真正的源码不是这样的,因为定时器是个宏任务,源码应该是微任务……这里存疑

function Promise(executer) {
   	......
    const success = (data) => {
        ......
        if (this.callbacks.length != 0) {
            setTimeout(() => {
                this.callbacks.forEach(item => {
                    item.onResolved();
                })
            });
        }
    }
    const fail = (data) => {
        ......
        if (this.callbacks.length != 0) {
            setTimeout(() => {
                this.callbacks.forEach(item => {
                    item.onRejected();
                })   
            });
        }
    }
    ......
}
//1.then方法的封装
Promise.prototype.then = function (onResolved, onRejected) {
    ......
    //执行then方法返回的还是Promise
    return new Promise((resolve, reject) => {
        //公共的改变返回Promise状态的方法封装起来
        ......
        //判断同步任务下走哪个回调
        if (this.PromiseState === 'resolved') {
            setTimeout(() => {
                changeState(onResolved);   
            });
            //如果抛出错误,不用再另外写try-catch,封装时写过了
            //所以任何Promise实例执行器函数出现错误,都可以直接捕获
        }
        if (this.PromiseState === 'rejected') {
            setTimeout(() => {
                changeState(onRejected);   
            });
        }
        ......
    })
}
......
17.class版本
class Promise {
    constructor(executer) {
        this.PromiseState = 'pending';//默认应该是等待
        this.PromiseResult = undefined;
        //定义一个属性来存放then函数的回调们
        this.callbacks = [];
        //参数executer是一个立即调用的函数
        //且该函数还接收两个参数,分别是两个函数
        const success = (data) => {
            //这是resolve对应的回调
            //状态改变后就不能再变,加个判断
            if (this.PromiseState !== 'pending') return;
            //1.改变对象的状态:pending=>resolved
            this.PromiseState = 'resolved';
            //2.改变对象的结果值
            this.PromiseResult = data;
            //3.如果是异步,要在以上步骤结束后,执行对应的回调
            // if(this.callback.onResolved){
            // this.callback.onResolved(data);
            // }
            if (this.callbacks.length != 0) {
                setTimeout(() => {
                    this.callbacks.forEach(item => {
                        item.onResolved();
                    })
                });
            }
        }
        const fail = (data) => {
            //这是reject对应的回调
            //状态改变后就不能再变,加个判断
            if (this.PromiseState !== 'pending') return;
            //1.改变对象的状态:pending=>resolved
            this.PromiseState = 'rejected';
            //2.改变对象的结果值
            this.PromiseResult = data;
            //3.如果是异步,要在以上步骤结束后,执行对应的回调
            // if(this.callback.onRejected) {
            // this.callback.onRejected(data);
            // }
            if (this.callbacks.length != 0) {
                setTimeout(() => {
                    this.callbacks.forEach(item => {
                        item.onRejected();
                    })
                });
            }
        }
        try {
            executer(success, fail);
        } catch (data) {
            //如果抛出错误,那么就执行下面的代码
            fail(data);
        }
    }
    //1.then方法的封装
    then(onResolved, onRejected) {
        //判断第二个回调是否存在,没有就写个默认的
        if (typeof onRejected !== 'function') {
            onRejected = err => {
                console.log('第二个默认回调执行');
                throw err;//一直往后找错误的回调
            }
        }
        //如果不写第一个回调,也能实现值的传递
        if (typeof onResolved !== 'function') {
            onResolved = res => {
                console.log('第一个默认回调执行');
                return res;
            };
        }
        //执行then方法返回的还是Promise
        return new Promise((resolve, reject) => {
            //公共的改变返回Promise状态的方法封装起来
            let changeState = (name) => {
                try {
                    const outcome = name(this.PromiseResult);
                    //判断回调返回结果是否是Promise类型
                    if (outcome instanceof Promise) {
                        //Promise类型的数据,返回状态要和它一致
                        outcome.then(res => {
                            //如果是成功的Promise,那一定会走这个回调
                            resolve(res);
                        }, err => {
                            //如果是失败的Promise,那一定会走这个回调
                            reject(err);
                        })
                    } else {
                        //非Promise类型,返回成功的Promise
                        resolve(outcome);
                    }
                } catch (e) {
                    reject(e);
                }
            }
            //判断同步任务下走哪个回调
            if (this.PromiseState === 'resolved') {
                setTimeout(() => {
                    changeState(onResolved);
                });
                //如果抛出错误,不用再另外写try-catch,封装时写过了
                //所以任何Promise实例执行器函数出现错误,都可以直接捕获
            }
            if (this.PromiseState === 'rejected') {
                setTimeout(() => {
                    changeState(onRejected);
                });
            }
            //如果是异步任务(先指定回调再改变状态再执行回调)
            if (this.PromiseState === 'pending') {
                //把回调存到该实例的属性上
                // this.callback.onResolved = onResolved;
                // this.callback.onRejected = onRejected;
                this.callbacks.push({
                    onResolved: () => {
                        changeState(onResolved);
                    },
                    onRejected: () => {
                        changeState(onRejected);
                    }
                })
            }
        })
    }

    //2.catch方法的封装
    catch(onRejected) {
        return this.then(undefined, onRejected);
    }
    //不是实例身上的方法,加上static关键字
    //3.resolve方法封装
    static resolve(data) {
        return new Promise((resolve, reject) => {
            if (data instanceof Promise) {
                data.then(res => {
                    resolve(res);
                }, err => {
                    reject(err);
                })
            } else {
                resolve(data);
            }
        })
    }
    //4.reject方法封装
    static reject(data) {
        return new Promise((resolve, reject) => {
            reject(data);
        })
    }
    //5.all方法封装
    static all(promiseArr) {
        return new Promise((resolve, reject) => {
            let flag = true; //判断是否所有都成功
            let arr = []; //存储成功的结果
            for (let i = 0; i < promiseArr.length; i++) {
                promiseArr[i].then(res => {
                    // arr.push(res); //这么写可能会由于异步出现顺序问题
                    arr[i] = res;  //这么写没事,是let的块级作用域效果
                }, err => {
                    reject(err);
                    flag = false;
                })
            }
            //循环结束后,判断是否所有Promise都是成功的
            if (flag) {
                resolve(arr);
            }
        })
    }
    //6.race方法封装
    static race(promiseArr) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promiseArr.length; i++) {
                promiseArr[i].then(res => {
                    resolve(res);
                }, err => {
                    reject(err);
                })
            }
        })
    }
}
四、异步编程终极解决方案
1.async函数

调用async函数返回一个Promise,该Promise的状态和值由async函数的返回值来决定

1、非Promise则返回一个成功的Promise,值为返回值
2、Promise则返回结果由它来决定
3、抛出错误则返回错误的Promise,值为抛出的值

async function main() {
    //1.非Promise则返回一个成功的Promise,值为返回值
    // return 521;
    //2.Promise则返回结果由它来决定
    return new Promise((resolve, reject) => {
        reject('error');
    })
    //3.抛出错误则返回错误的Promise,值为抛出的值
    // throw '错误';
}
console.log(main());
2.await关键字

1、await后边一般跟的是Promise对象,也可以是其他值
2、如果跟的是Promise对象,await返回的是Promise成功的值

async function dj() {
    const p = new Promise((resolve, reject) => {
        resolve('ok');
    })
    const res = await p;
    console.log(res); //ok
}
dj();

3、如果跟的是其他值,直接将此值作为返回值
4、如果await后面跟的是失败的Promise,会抛出异常,需要try-catch捕获

async function dj() {
    const p = new Promise((resolve, reject) => {
        reject('错误嗷嗷');
    })
    try{
        const res = await p;
        console.log(res); 
    }catch(e) {
        console.log(e);
    }
}
dj();

5、await只能写在async中,但async中可以没有await

3.发送ajax请求
function sendAjax() {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.responseType = 'json';  //指定返回结果格式
        xhr.open('GET', 'https://ku.qingnian8.com/dataApi/news/navlist.php');
        xhr.send();
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr.status);
                }
            }
        }
    });
}
async function getNews() {
    try{
        let result = await sendAjax();
        console.log(result);
    }catch(e) {
        console.log(e);
    }
}
getNews();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值