Promise学习记录(手写promise async和await)

165 阅读9分钟

Promise概念

promise是是一门新的技术(ES6规范),是JS中解决异步编程的新方案。(旧方案是单纯使用回调函数)。

从语法上来说,promise是一个构造函数,从功能上来说,Promise对象用来封装一个异步操作并可以获取成功或失败的结果值。

异步编程:fs文件操作,数据库操作,AJAX,定时器。

Promise支持链式调用,可以解决回调地狱问题。

回调地狱问题:

多层回调函数的相互嵌套,就形成了回调地狱。如图示例:

image.png

缺点:不利于阅读,不便于异常处理。

promise指定回调函数的方式更加灵活

  1. 旧的:必须在启动异步任务前指定‘

  2. promise: 启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定多个)

Promise初体验

<!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>Document</title>
</head>

<body>
    <div class="container">
        <h2>初体验</h2>
        <button id="btn">点击抽奖</button>
    </div>
    <script>
        // 生成随机数
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1)) + m - 1
        }
        // 点击抽奖,2s后显示是否中奖,中奖概率为30%,
        // 若中奖弹出,恭喜中奖
        // 若未中奖,弹出再接再厉
        // 获取按钮元素
        let btn = document.getElementById('btn')
        // 绑定单机事件
        btn.addEventListener('click', function () {
            // 回调函数形式实现
            // setTimeout(()=>{
            //     let n = rand(1,100)
            //     // 30%的概率
            //     if(n <= 80){
            //         alert('恭喜中奖了')
            //     }else{
            //         alert('再接再励')
            //     }
            // },1000)

            // promise形式实现

            // promise实例化时要接收一个参数,是函数类型的值,
            // 这个函数还有两个形参,分别是resolve(解决)和reject(拒绝),也是函数类型的数据
            // 当异步任务成功时候调用resolve,当异步任务失败时调reject.
            // promise可以包裹一个异步操作
            const p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    let n = rand(1, 100)
                    // 30%的概率
                    if (n <= 80) {
                        resolve() //调完后可将promise对象的状态设为成功
                    } else {
                        reject() //调完后可将promise对象的状态设置为失败
                    }
                }, 1000)
            })
            // 状态失败和成功后要做的事可以放在这个promise对象的then方法里
            // 这个then方法可以接收两个函数类型的参数,第一个是对象成功时的回调,第二个是对象失败的回调
            p.then(() => { alert('恭喜中奖了') }, () => { alert('再接再励') })
        })
    </script>
</body>

</html>

Promise初体验2

<!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>Document</title>
</head>

<body>
    <div class="container">
        <h2>初体验</h2>
        <button id="btn">点击抽奖</button>
    </div>
    <script>
        // 生成随机数
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1)) + m - 1
        }
        // 点击抽奖,2s后显示是否中奖,中奖概率为30%,
        // 若中奖弹出,恭喜中奖
        // 若未中奖,弹出再接再厉
        // 获取按钮元素
        let btn = document.getElementById('btn')
        // 绑定单机事件
        btn.addEventListener('click', function () {
            // 回调函数形式实现
            // setTimeout(()=>{
            //     let n = rand(1,100)
            //     // 30%的概率
            //     if(n <= 80){
            //         alert('恭喜中奖了')
            //     }else{
            //         alert('再接再励')
            //     }
            // },1000)

            // promise形式实现

            // promise实例化时要接收一个参数,是函数类型的值,
            // 这个函数还有两个形参,分别是resolve(解决)和reject(拒绝),也是函数类型的数据
            // 当异步任务成功时候调用resolve,当异步任务失败时调reject.
            // promise可以包裹一个异步操作
            const p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    let n = rand(1, 100)
                    // 50%的概率
                    if (n <= 50) {
                        resolve(n) //调完后可将promise对象的状态设为成功
                    } else {
                        reject(n) //调完后可将promise对象的状态设置为失败
                    }
                }, 1000)
            })
            // 状态失败和成功后要做的事可以放在这个promise对象的then方法里
            // 这个then方法可以接收两个函数类型的参数,第一个是对象成功时的回调,第二个是对象失败的回调
            p.then((value) => { alert(`恭喜中奖号码为${value}`) }, (reason) => { alert(`再接再励${reason}`) })

            // 需求增进:把那个n也显示在alert弹出的内容中
            // promise除了封装异步任务之外,还可以获取异步任务当中成功和失败的结果值(借助resolve和reject)
        })
    </script>
</body>

</html>

Promise实践练习--fs模块

const fs = require('fs')

// 回调函数的形式
// fs.readFile('./resource/content.txt',(err,data) => {
//     //如果出错,则抛出错误
//     if(err) throw err
//     //输出文件内容
//     console.log(data.toString());
// })

// Promise形式
let p = new Promise((resolve,reject) => {
    fs.readFile('./resource/content.txt',(err,data) => {
        //如果失败
        if(err) reject(err)
        // 如果成功
        resolve(data)
    })
})

// 调用p的then方法
p.then((value)=>{
    console.log(value.toString())
},(reason)=>{
    console.log(err)
})

Promise实践练习--AJAX

<!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>Document</title>
</head>

<body>
    <h1>Promise封装AJAX</h1>
    <button id="btn">点击发送AJAX</button>

    <script>
        let btn = document.querySelector('#btn')
        btn.addEventListener('click', function () {
            const p = new Promise((resolve, reject) => {
                let xhr = new XMLHttpRequest()
                xhr.open('get', 'https://api.apiopen.top/getJok')
                xhr.send()
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(xhr.response)
                        } else {
                            reject(xhr.status)
                        }
                    }
                }
            })
            p.then(value => {
                console.log(value)
            }, reason => {
                console.warn(reason)
            })

        })

    </script>
</body>

</html>

Promise封装fs读取文件操作

// 封装一个函数mineReadFile读取文件内容
// 参数:path 文件路径
// 返回:Promise对象

// 封装函数
function mineReadFile(path) {
    return new Promise((resolve, reject) => {
        require('fs').readFile(path, (err, data) => {
            if (err) reject(err)
            resolve(data)
        })
    })
}

// 读取文件
mineReadFile('./resource/content.txt').then(value => { console.log(value.toString()) }, reason => { console.log(err) })

util模块内置的promisify方法进行Promise风格转换

// 引入util模块
const util = require('util')
// 引入fs模块
const fs = require('fs')
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile)
mineReadFile('./resource/content.txt').then(value => {console.log(value.toString())})

Promise封装AJAX请求

<!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>Document</title>
</head>

<body>
    <script>
        // 封装一个函数 sendAJAX 发送 GET AJAX请求
        // 参数 url 
        // 返回结果 Promise对象
        function sendAJAX(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest()
                xhr.responseType = 'json'
                xhr.open('GET', url)
                xhr.send()
                xhr.onreadystatechange() = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(xhr.response)
                        } else {
                            reject(xhr.status)
                        }
                    }
                }
            })

        }

        sendAJAX('https://api.apiopen.top/getJoke').then(
            value => {
                console.log(value)
            },
            reason => {
                console.log(reason)
            }
        )
    </script>
</body>

</html>

promise对象状态属性介绍

promise的状态是实例对象中的一个内置属性,【PromiseState】,有三个值

  1. pending 未决定的
  2. resolved/fullfilled 成功
  3. rejected 失败

promise的状态改变

  1. pending 变为 resolved
  2. pending 变为 rejected

只有这两种改变,且一个promise对象只能改变一次,无论变为成功或失败,都会有一个结果数据,成功的结果一般称为value,失败的结果一般称为reason

promise对象结果值属性介绍

PromiseResult是promise对象结果属性值,保存着异步任务【成功/失败】的结果。 只有resolve和reject函数可以这个属性值进行赋值。

Promise基本流程

image.png

Promise的api

1.promise包裹的是同步任务

 <script>
        let p = new Promise((resolve,reject)=>{
            console.log(111);
        })
        console.log(222);
        
    </script>

2.promise实例对象的catch方法,用于指定失败的回调函数。

   let p = new Promise((resolve,reject)=>{
          reject('error')
        })
        p.catch(reason => {
            console.log(reason);
        })      
  1. Promise的resolve方法

快速返回一个Promise对象,分以下两种情况

  <script>
     let p = Promise.resolve(521) //resolve不属于Promise的实例对象,直接是Promise调用的
    //  如果传入的参数为非promise对象,则返回的结果为状态成功的promise对象。
    console.log(p)
  </script>

image.png

 let p1 = Promise.resolve(new Promise((resolve, reject) => {
            reject('err')
        }))
        // 如果传入的参数为Promise对象,则参数的结果决定了resolve的结果,里面的promise是啥结果,这个resolve就是啥结果。
        console.log(p1);

image.png

  1. Promise的reject方法

返回一个失败的Promise对象,不管里面传什么。传入什么,返回的Promise失败的结果就是什么

 <script>
        let p = Promise.reject(new Promise((resolve, reject) => {
            resolve('kd')
        }))
        console.log(p)
    </script>

image.png

  1. Promise函数对象的all方法

接收一个参数,一般为Promise组成的新数组,返回一个新的Promise对象,如果接收Promise数组中每个Promise对象状态都成功,它返回的Promise对象状态成功,如果数组中有一个是失败的,返回的结果就是失败的Promise对象。而且返回的Promise对象的结果是数组中每个Promise对象结果组成的数组。它失败的结果是这个Promise对象数组中失败的那个的结果。

    <script>
        let p1 = new Promise((resolve, reject) => {
            resolve('OK');
        })
        // let p2 = Promise.resolve('Success');
        let p2 = Promise.reject('Error');
        let p3 = Promise.resolve('Oh Yeah');
        
        //
        const result = Promise.all([p1, p2, p3]);

        console.log(result);
    </script>
  1. Promise函数对象的race方法

接收一个参数,一般为Promise组成的新数组,返回一个新的Promise对象,这个新对象的结果由数组中第一个改变状态的Promise对象决定。

    <script>
        let p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        })
        let p2 = Promise.resolve('Success');
        let p3 = Promise.resolve('Oh Yeah');

        //调用
        const result = Promise.race([p1, p2, p3]);

        console.log(result);
    </script>

Promise的几个关键问题

  1. 代码当中如何改变Promised对象的状态,有三种方式
 <script>
        let p = new Promise((resolve, reject) => {
            //1. resolve 函数
            // resolve('ok'); // pending   => fulfilled (resolved)
            //2. reject 函数
            // reject("error");// pending  =>  rejected 
            //3. 抛出错误
            // throw '出问题了';
        });

        console.log(p);
    </script>

2.Promise对象指定多个成功或者失败的回调函数,它都会调用吗? 当Promise改变对应状态时他们都会调用。

    <script>
        let p = new Promise((resolve, reject) => {
            // resolve('OK');
        });

        ///指定回调 - 1
        p.then(value => {
            console.log(value);
        });

        //指定回调 - 2
        p.then(value => {
            alert(value);
        });
    </script>
  1. 改变Promise对象的状态与指定回调函数谁先谁后?
  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  1. promise.then()返回的新 promise 的结果状态由什么决定?

(1) 简单表达: 由 then()指定的回调函数执行的结果决定

(2) 详细表达:

  • 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
  • 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
  • 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
   <script>
        let p = new Promise((resolve, reject) => {
            resolve('ok');
        });
        //执行 then 方法
        let result = p.then(value => {
            // console.log(value);
            //1. 抛出错误
            // throw '出了问题';
            //2. 返回结果是非 Promise 类型的对象
            // return 521;
            //3. 返回结果是 Promise 对象
            // return new Promise((resolve, reject) => {
            //     // resolve('success');
            //     reject('error');
            // });
        }, reason => {
            console.warn(reason);
        });

        console.log(result);
    </script>
  1. promise 如何串连多个操作任务?

(1) promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用

(2) 通过 then 的链式调用串连多个同步/异步任务

 <script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        });

        p.then(value => {
            return new Promise((resolve, reject) => {
                resolve("success");
            });
        }).then(value => {
            console.log(value);
        }).then(value => {
            console.log(value);
        })
    </script>

6.Promise对象的异常穿透

(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,

(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理

<script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
                // reject('Err');
            }, 1000);
        });

        p.then(value => {
            // console.log(111);
            throw '失败啦!';
        }).then(value => {
            console.log(222);
        }).then(value => {
            console.log(333);
        }).catch(reason => {
            console.warn(reason);
        });
        //只在最后指定错误回调,如果第一个Promise状态变为失败,
        //则中间的那些不管直接就指定执行错误回调的现象叫做异常穿透。
    </script>
  1. 中断 promise 链?

(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数

(2) 办法: 在回调函数中返回一个 pending 状态的 promise。

  <script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        });

        p.then(value => {
            console.log(111);
            //有且只有一个方式,这个Promise对象的状态为pending,则中断。
            //因为romise改变对应状态时他们才会调用
            return new Promise(() => {});
        }).then(value => {
            console.log(222);
        }).then(value => {
            console.log(333);
        }).catch(reason => {
            console.warn(reason);
        });
    </script>

自定义Promise(手写Promise)

class Promise {
    // 构造方法
    //覆盖Promise,new的时候是new我们这个
    // executor(执行器函数),对应Promise接收的哪个函数参数
    constructor(executor) {
        // 为Promise实例对象添加属性promiseState和promiseResult,这里的this执行实例对象
        this.promiseState = 'pending'
        this.promiseResult = null
        this.callbacks = [] //数组,为了then的回调都执行
        const self = this
        // 定义以下resolve函数和reject函数
        // data是resolve函数和reject函数调用的形参
        function resolve(data) {
            // 加上一个判断,让Promise对象的状态只能更改一次
            if (self.promiseState !== 'pending') return
            // resolve函数一调用
            // 1.改变Promise对象状态(promiseState)
            self.promiseState = 'fullfilled'
            // 2.改变Promise对象结果值(promiseResult)
            self.promiseResult = data
            // 调用成功的的回调函数,异步任务结束后在这里调
            // 记得这里也让他们变成异步的
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onResolved(data)
                })
            });

        }
        function reject(data) {
            if (self.promiseState !== 'pending') return
            // 1.改变Promise对象状态(promiseState)
            self.promiseState = 'rejected'
            // 2.改变Promise对象结果值(promiseResult)
            self.promiseResult = data
            // 调用失败的的回调函数,异步任务结束后在这里调
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onRejected(data)
                })
            });
        }

        // throw抛出异常也要改变promise实例的状态,要想到try,catch能处理异常

        // 同步调用执行器函数
        // executor()这个函数,它调用的时候要传resolve和reject
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }

    // then方法封装
    // 添加then方法
    // onResolve对应then的第一个函数参数,onRejected对应then的第二个函数参数
    then(onResolved, onRejected) {
        const self = this
        // 判断回调函数参数,第二个onRejected没传时设置默认值
        if (typeof onRejected !== 'function') {
            onRejected = reason => {
                throw reason
            }
        }
        if (typeof onResolved !== 'function') {
            // 值传递
            onResolved = value => value
            // value => {return value}
        }
        // then方法返回的要是个Promise对象
        return new Promise((resolve, reject) => {
            // 封装函数
            function callback(type) {
                try {
                    let result = type(self.promiseResult)
                    //这里的this.promiseResult实参对应value
                    if (result instanceof Promise) {
                        // 如果是Promise类型的对象     // 这里的result是then里面的新Promise
                        result.then(
                            v => { resolve(v) },
                            r => { reject(r) })
                    } else {
                        // 结果的对像状态为成功
                        resolve(result) //这里调用的this就是then方法里面的对象
                    }
                } catch (e) {
                    reject(e)
                }
            }
            // 这里的this也指向Promise实例对象,因为我们调用时是"p.then"这样调用的
            if (this.promiseState === 'fullfilled') {
                // then里面的回调是异步执行的,加个定时器让它变成异步
                setTimeout(() => {
                    callback(onResolved)
                });
            }
            if (this.promiseState === 'rejected') {
                setTimeout(() => {
                    callback(onRejected)
                });
            }
            // 为了实现执行器是异步的时,then里面的回调调用
            //判断pending状态
            if (this.promiseState === 'pending') {
                // 保存回调函数,不能直接执行then的回调,因为这时候状态还不确定
                this.callbacks.push({
                    onResolved: function () {
                        callback(onResolved)
                    },
                    onRejected: function () {
                        callback(onRejected)
                    }
                })
            }
        })
    }

    // catch方法封装
    catch(onRejected) {
        return this.then(undefined, onRejected)
    }

    // resolve方法封装
    //添加Promise的resolve方法
    // static关键字表明他是静态资源,属于类而不属于构造函数
    static resolve(value) {
        //  返回结果Promise对象
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then(v => {
                    resolve(v)
                }, r => {
                    reject(r)
                })
            } else {
                // 不是Promsie对象就将返回的那个状态设置为成功
                resolve(value)
            }
        })
    }

    // reject方法封装
    // 添加Promise的reject方法
    static reject(reason) {
        //  返回结果Promise对象
        return new Promise((resolve, reject) => {
            reject(reason)
        })
    }

    // all方法封装
    //添加Promise的all方法
    static all(promises) {
        // 返回结果为Promise对象
        return new Promise((resolve, reject) => {
            let count = 0
            let arr = [] //存放返回结果的数组
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    // promises[i]如果成功就会调用这个回调,得知这个对象的状态是成功的
                    // 遍历的每个promise对象状态都成功,才能调用resolve()去修改返回的对象状态
                    count++
                    // 将当前promise对象成功的结果存入到arr中
                    arr[i] = v
                    // 判断,每次循环是成功的就给count加一个,如果count和数组长度相同就是都是成功的
                    if (count === promises.length) {
                        resolve(arr)
                    }
                }, r => {
                    reject(r)
                })
            }
        })
    }

    // race方法封装
    static race(promises) {
        // 返回结果为Promise对象
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    // 修改返回对象的状态为成功
                    resolve(v)
                }, r => {
                    // 修改返回对象的状态为失败
                    reject(r)
                })
            }
    
        })
    }
}

async函数

函数的返回值为promise对象

promise对象的结果由async函数执行的返回值决定

 <script>
        async function main(){
            // 1. 如果返回值是个非promise对象的数据,结果就是成功的状态,然后reuslt就是return后面的
            // return "512"
            //2. 如果返回的是个promise对象的状态,它返回promise对象受这个promise影响
            // return new Promise((resolve, reject) => {
            //     resolve('djj')
            // })
            // 3. 如果抛出异常,返回的promise对象的状态就是失败的,并且result就是throw 后面的值
            throw 'fjfjfjj'
        }
        let result = main()
        console.log(result);
    </script>

await表达式

await右侧的表达式一般为promsie对象,但是也可以是其他的值。

如果右侧表达式是promise对象,await返回的是promise成功的值。

如果表达式是其他值,直接将此值作为await的返回值。

注意:

await必须写在async函数中,但是async函数可以没有await.

如果await的promise失败了,就会抛出异常,需要通过try-catch捕获处理。

<script>
        async function main(){
            let p = new Promise((resolve, reject) => {
                // resolve('OK');
                reject('Error');
            })
            //1. 右侧为promise的情况
            // let res = await p;
            //2. 右侧为其他类型的数据
            // let res2 = await 20;
            //3. 如果promise是失败的状态
            try{
                let res3 = await p;
            }catch(e){
                console.log(e);
            }
        }

        main();
    </script>

async和await结合实践

/**
 * resource  1.html  2.html 3.html 文件内容
 */

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

//回调函数的方式
// fs.readFile('./resource/1.html', (err, data1) => {
//     if(err) throw err;
//     fs.readFile('./resource/2.html', (err, data2) => {
//         if(err) throw err;
//         fs.readFile('./resource/3.html', (err, data3) => {
//             if(err) throw err;
//             console.log(data1 + data2 + data3);
//         });
//     });
// });

//async 与 await
async function main(){
    try{
        //读取第一个文件的内容
        let data1 = await mineReadFile('./resource/1x.html');
        let data2 = await mineReadFile('./resource/2.html');
        let data3 = await mineReadFile('./resource/3.html');
        console.log(data1 + data2 + data3);
    }catch(e){
        console.log(e.code);
    }
}

main();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>async与await结合发送AJAX</title>
</head>
<body>
    <button id="btn">点击获取段子</button>
    <script>
        //axios
        function sendAJAX(url){
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.responseType = 'json';
                xhr.open("GET", url);
                xhr.send();
                //处理结果
                xhr.onreadystatechange = function(){
                    if(xhr.readyState === 4){
                        //判断成功
                        if(xhr.status >= 200 && xhr.status < 300){
                            //成功的结果
                            resolve(xhr.response);
                        }else{
                            reject(xhr.status);
                        }
                    }
                }
            });
        }

        //段子接口地址 https://api.apiopen.top/getJoke
        let btn = document.querySelector('#btn');

        btn.addEventListener('click',async function(){
            //获取段子信息
            let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');
            console.log(duanzi);
        });
    </script>
</body>
</html>