Promise学习

64 阅读6分钟
介绍

es6引入的,进行异步编程的新的解决方案,语法上来说是构造函数,可以封装异步任务,并且对结果进行处理 Promise最大的好处在于支持链式调用,可以解决回掉地狱的问题,在指定回掉错误处理这块更加的灵活和方便。

Promise介绍与基本使用
  1. 抽象表达
    • es6引入的
    • 进行异步编程的新的解决方案,旧的解决方案是使用回掉函数
  2. 具体表达
    • 语法上来说是构造函数
    • 可以封装异步任务,并且可以获取成功/失败的结果值
异步编程
  • fs文件操作(fs是nodeJs使用)
require(`js`).readFile('./index.html', (err, data)=>{})
  • 数据库操作
  • AJAX
  • 定时器
setTimeout(()=>{}, 2000)
回调地狱
asyncFunc1(opt, (...args1) => {
    asyncFunc2(opt, (...args2) => {
        asyncFunc3(opt, (...args3) => {
            asyncFunc4(opt, (...args4) => {
                //some operation
            })
        })
    })
})

Promise对象状态属性

1. 实例对象中的一个属性 『PromiseState』 具有以下三种状态
  1. pending 未决定
  2. resolved / fullfilled 成功
  3. rejected 失败
2. promise 的状态改变
  1. pending 变为 resolved
  2. pending 变为 rejected

Promise对象的值

实例对象种的另一个属性 **PromiseResult** 保存着异步任务·对象  成功/失败  的结果
  • resolve
  • reject
Promise的基本流程

image.png

Promise的基本使用

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

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


</script>
  1. Promise封装文件读取fs
//封装一个函数,读取文件内容
//参数: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(reason);
})

3util.promisify方法

可以将函数直接变成promise的封装方式,不用再去手动封装

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


mineReadFile('./resource/content.txt')
.then(value => {
    console.log(value.toString());
}, reason => {
    console.log(reason);
})

Promis中常用API概述

1.Promise构造函数:Promise(excutor){}

(1) executor 函数: 执行器 (resolve, reject) => {}

(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}

(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}

<script>
    ///打印顺序 111,222
    //说明: executor 会在 Promise 内部立即`同步调用`,异步操作在执行器中执行,换话说Promise支持同步也支持异步操作
    let p = new Promise((resolve, reject) => {
        console.log(111);//同步调用的,很重要
    })
    console.log(222);
</script>
2.Promise.prototype.then 方法: (onResolved, onRejected) => {}

(1) onResolved 函数: 成功的回调函数 (value) => {}

(2) onRejected 函数: 失败的回调函数 (reason) => {}

说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象

3.Promise.prototype.catch 方法: (onRejected) => {}

(1) onRejected 函数: 失败的回调函数 (reason) => {}

说明: then()的语法糖, 相当于: then(undefined, onRejected)

(2) 异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中

4. Promise.resolve 和 Promise.reject方法

<script>
    //
    //如果传入的参数为   非Promise类型的对象(数字。。。)。则返回的结果为成功promise对象
    //如果传入的参数为   Promise类型的对象(数字。。。)。则参数的结果决定了resolve的结果
    let p1 = Promise.resolve(521);
    let p2 = Promise.resolve(new Promise((resolve, reject) => {
        // resolve('ok')
        reject('err')
    }))
    console.log(p1);
    console.log(p2);
    p2.catch(reason => {
        console.log(reason);
    })

    let p3 = Promise.reject(521);
    console.log(p3);

    let p4 = Promise.reject(new Promise((resolve, reject) => {
        resolve('ok')
        // reject('err')
    }))

    console.log('p4', p4);
</script>

5.Promise.all方法

说明: 返回一个新的 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>

6.Promise.race方法

由第一个改变状态的promise对象的结果决定

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

    const result = Promise.race([p1, p2, p3])
    console.log(result);
</script>

Promise的几个关键问题

Ⅰ-如何改变 promise 的状态?

(1) resolve(value): 如果当前是 pending 就会变为 resolved

(2) reject(reason): 如果当前是 pending 就会变为 rejected

(3) 抛出异常: 如果当前是 pending 就会变为 rejected

<script>
    let p = new Promise((resolve, reject) => {
        //1. resolve函数  pending =》 resolved(fulfilled)
        // resolve('ok')
        //2. resolve函数  pending =》 rejected
        // resolve('error')
        //3.抛出错误
        throw '出问题了'
    });

    console.log(p);
</script>

Ⅱ-一个 promise 指定多个成功/失败回调函数, 都会调用吗?

当 promise 改变为对应状态时都会调用,改变状态后,多个回调函数都会调用,并不会自动停止

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

    p.then(value => {
        console.log(value);
    })

    p.then(value => {
        alert(value)
    })
</script>

改变promise状态和指定回调函数,谁先执行?

(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调

​ 先指定回调再改变状态(异步):先指定回调--> 再改变状态 -->改变状态后才进入异步队列执行回调函数

​ 先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调

(2) 如何先改状态再指定回调? -->注意:指定并不是执行

​ ① 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作

​ ② 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法

(3) 什么时候才能得到数据?

​ ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据

​ ② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

    <script>
        //指定回调:在then中放入成功或者失败状态,对应状态有特定的回调函数,对应关系,但没有实施
        let p = new Promise((resolve, reject) => {
            // resolve('ok') //1  同步任务
            setTimeout(() => {
                resolve('ok') //3    异步任务
            }, 1000);
        });

        //2
        p.then((result) => {
            console.log(result);
        }).catch((err) => {
            
        });
    </script>

promise.then()返回的新 promise 的结果状态由什么决定?

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

(2) 详细表达:

​ ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常

​ ② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值

​ ③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果

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

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>

Ⅵ-promise 异常传透?

  • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
  • 前面任何操作出了异常, 都会传到最后失败的回调中处理
getJSON('./hong.json')
   .then(function(posts) { throw new Error('抛出异常') })
  .then(res=>console.log(res),e=>console.log('被then的错误回调捕获',e) )
   .catch(function(error) {
		 // 处理 getJSON 和 前一个回调函数运行时发生的错误
 		console.log('错误捕获: ', error);
  });
//执行结果: 被then的错误回调捕获 Error: 抛出异常

/******************** 利用异常穿透 ****************************************/
getJSON('./hong.json')
   .then(function(posts) { throw new Error('抛出异常') })
  .then(res=>console.log(res) ) //此处差异,不指定 reject 回调,利用异常穿透传到最后
   .catch(function(error) {
 		console.log('错误捕获: ', error);
  });
//执行结果:  错误捕获:  Error: 抛出异常

注:可以在每个then()的第二个回调函数中进行err处理,也可以利用异常穿透特性,到最后用catch去承接统一处理,两者一起用时,前者会生效(因为err已经将其处理,就不会再往下穿透)而走不到后面的catchimage-20210927105504988

Ⅶ- 中断 promise 链?

关键问题2中,可以得知,当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有5个then(),但其中有条件判断,如当我符合或者不符合第三个then条件时,要直接中断链式调用,不再走下面的then,该如何?

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

(2) 办法: 在回调函数中返回一个 pendding 状态的promise 对象

<script>
    let p = new Promise((resolve, reject) => {
        setTimeout(() => { 
            resolve('OK');
        }, 1000);
    });
    p.then(value => {
        return new Promise(() => {});
    })//有且只有这一个方式
    .then(value => { 
        console.log(222);
    })
    .then(value => {
        console.log(333);
    })
    .catch(reason => {
        console.warn(reason);
    });
</script>

async和await

async
  1. 函数的返回值为 promise 对象
  2. promise 对象的结果由 async 函数执行的返回值决定
await
  1. await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
  2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值
  3. 如果表达式是其它值, 直接将此值作为 await 的返回值