12.JS ES6 Promise构造函数

168 阅读6分钟

ES6异步编程解决方案:Promise

用于封装异步操作,获取成功或失败的结果

1. Promise构造函数: Promise(excutor) {}
2. Promise.prototype.then 方法
3. Promise.prototype.catch 方法
4. Promise 的三种状态:Pending初始状态、resolved(fulfilled)成功、rejected失败
5. Promise 指定的then、catch回调为微任务(先执行微任务回调队列,后执行微任务回调队列,如setimeout)

实例化 Promise 对象

如何改变一个Promise实例的状态?

  1. 执行resolve(value):如果当前是pending就会变为fulfilled
  2. 执行reject(reason):如果当前是pending就会变为rejected
  3. 执行器函数(executor)抛出异常:如果当前是pending就会变为rejected(可以手动throw抛异常)
const p = new Promise(function(resolve, reject) {
    // 异步操作:
    setTimeout(function() {
        let data = '获取数据成功';
        // 调用resolve函数,来改变 p(Promise) 的状态:(成功)
        resolve(data);
    },1000);
});

// 调用 promise 对象的 then 方法的第一个回调函数(包含两个成功和失败函数类型的参数)
p.then(function(value) {
    console.log(value);  // 获取数据成功
}, function(reason) {
    console.error(reason);
})
const p = new Promise(function(resolve, reject) {
    // 异步操作:
    setTimeout(function() {
        let err = '获取数据失败';
        // 调用reject函数,来改变 p(Promise) 的状态:(失败)
        resolve(err);
    },1000);
});

// 调用 promise 对象的 then 方法的第二个回调函数(包含两个成功和失败函数类型的参数)
p.then(function(value) {
    console.log(value);
}, function(reason) {
    console.error(reason);  // 获取数据失败
})

Promise 封装

// 引入node.js API 的 fs 模块
const fs = require('fs');

const p = new Promise(function(resolve, reject) {
    fs.readFile("./file.text", (err, data)=>{
        // 失败:
        if(err) reject(err);  // 此时p状态为失败
        // 成功:
        resolve(data);  // 此时p状态为成功
    });
});

// p状态为成功时调用第一个函数参数,p状态为失败时调用第二个函数参数
p.then(function(value) {
    console.log("读取成功!", value);  // 成功回调(异步)打印数据:data
}, function(reason) {
    console.log("读取失败!", reason);  // 失败回调(异步)抛出错误:err
});

console.log("end")  // 异步输出结果:end 读取成功!data / end 读取失败!err

Promise 封装 Ajax(解决回调地狱)

优势

  1. 指定回调函数的方式更加灵活:
    • 旧的:必须在启动异步任务前指定
    • promise:启动异步任务→返回promie对象→给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
  2. 支持链式调用,可以解决回调地狱问题
    • 什么是回调地狱:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件
    • 回调地狱的弊病:代码不便于阅读、不便于异常的处理
    • 一个不是很优秀的解决方案:then的链式调用
    • 终极解决方案:async/await(底层实际上依然使用then的链式调用)

简单封装:

const p = new Promise((resolve, reject)=>{
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState === 4){
            if(xhr.status >= 200 && xhr.status < 300){
                resolve(xhr.response);
            }else {
                reject('请求出错!')
            }
        }
    }
    xhr.open('GET', 'https://api.xxx');
    xhr.responseType = 'json'
    xhr.send();
})

// 状态回调:
p.then(
    (value)=>{console.log('成功', value);},
    (reason)=>{console.log('失败', reason);}
)

优化:

function sendAjax(url,data){
    const p = new Promise((resolve,reject)=>{
        //实例xhr
        const xhr = new XMLHttpRequest()
        //绑定监听
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState === 4){
                if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
                else reject('请求出问题了');
            }
        }
        //整理参数
        let str =''
        for (let key in data){
            str += `${key}=${data[key]}&`
        }
        str = str.slice(0,-1)
        xhr.open('GET',ur1+'?'+str)
        xhr.send()
    })
    return p
}

then的链式调用解决回调地狱(ES6)

Promise(实例).then()返回的是一个“新的Promise实例”,它的值和状态由什么决定?🔺

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

  2. 详细表达:

    • 如果then所指定的回调返回的是非Promise值a:那么“新Promise实例”状态为:成功(fulfilled), 成功的value为a
    • 如果then所指定的回调返回的是一个Promise实例p:那么“新Promise实例”的状态、值,都与p一致
    • 如果then所指定的回调抛出异常:那么“新Promise实例”状态为rejected, reason为抛出的那个异常
const x = sendAjax('https://api.xxx01', {page:1, count:1})
x.then(
    // 1. then也返回一个Promise对象,return将请求后的Promise对象传给then代表请求成功,再去调用下一个then🔺
    /* 2. 失败的回调为reason为undefined,undefined返回给then时状态为成功,需中断Promise链:
          在失败的回调函数中返回一个pendding状态的Promise实例,return new Promise(()=>{})
    */
    (value) => {console.log('第1次请求成功',value);
        return sendAjax('https://api.xxx02',{page:2, count:1}) // 开始下一次请求
    },
    (reason) => {console.log('第1次请求失败',reason); return new Promise(()=>{})}
)

.then(
    value => {
        console.log('第2次请求成功',value);
        return sendAjax('https://api.xxx03',{page:3, count:1})
    },
    reason => {console.log('第2次请求失败',reason); return new Promise(()=>{})}
)

.then(
    value => {
        console.log('第3次请求成功',value);
        return sendAjax('https://api.xxx04',{page:4, count:1})
    },
    reason => {console.log('第3次请求失败',reason);}
)

错误穿透catch:

  1. 当使用promise的then链式调用时,可以在最后用catch指定一个失败的回调,
  2. 前面任何操作出了错误,都会传到最后失败的回调中处理了
  3. 备注:如果不存在then的链式调用,就不需要考虑then的错误穿透
function sendAjax(url,data,index){
    const p = new Promise((resolve,reject)=>{
        //实例xhr
        const xhr = new XMLHttpRequest()
        //绑定监听
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState === 4){
                if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
                else reject(`第${index}次请求出问题了`); // 错误穿透索引参数
            }
        }
        //整理参数
        let str =''
        for (let key in data){
            str += `${key}=${data[key]}&`
        }
        str = str.slice(0,-1)
        xhr.open('GET',ur1+'?'+str)
        xhr.send()
    })
    return p
}
sendAjax('https://api.xxx01',{page:1},1)
.then(
    value => {console.log('第1次请求成功',value); return sendAjax('https://api.xxx02',{page:2},2)}
)
.then(
    value => {console.log('第2次请求成功',value); return sendAjax('https://api.xxx03',{page:3},3)}
)
.then(
    value => {console.log('第3次请求成功了',value);}
)
.catch(
    reason => console.log(reason)
)

其他方法

Promise.prototype.catch方法:(只处理失败回调,需要先实例化对象)

Promise(实例).catch(onRejected) onRejected: 失败的回调函数(reason) => {} 说明: catch方法是then方法的语法糖,相当于: then(undefined, onRejected)

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(100)
    },1000)
})
p.catch(
    reason => {console.log('失败',reason);}
)

Promise.resolve方法: (Promise自身方法,不需要实例)

const p = Promise.resolve(value) 说明:用于快速返回一个状态为fulfilled或rejected的Promise实例对象 备注:value值可能是“非Promise值”(p.then成功)或“Promise”(值为失败的Promise时p.then为失败)

const p = Promise.resolve(100)
p.then(
    value => {console.log('成功了',value);},
    reason => {console.log('失败了',reason);}
)

Promise.reject方法: Promise.reject方法(reason)

说明:用于快速返回一个状态为rejected的Promise实例对象

Promise.all方法: Promise.all(promiseArr)

promiseArr: 包含n个Promise实例的数组[p1,p2,p3...] 说明:返回一个新的Promise实例,只有所有的promise都成功才成功(值为实例返回值数组),只要有一个失败就直接返回失败

Promise.race方法: Promise.race (promiseArr)

promiseArr: 包含n个Promise实例的数组 说明:返回一个新的Promise实例,成功还是很失败?以最先出结果的promise为准

Promise配合async与await使用

<script>
    const p1 = new Promise( (resolve, reject)=>{
        setTimeout(()=>{
        resolve('a')
        }, 1000)
    })
    const p2 = new Promise( (resolve, reject)=>{
        setTimeout(()=>{
        reject('一些错误')
        }, 2000)
    })
    const p3 = new Promise((resolve, reject)=>{
        setTimeout(()=>{
        resolve('c')
        }, 4000)
    })

    !(async()=>{
        try{
            const result1 = await p1
            console.log(result1);
            const.result2 = await p2
            console.log(result2);
            const result3 = await p3
            console.log(result3);
        } catch (error){
            console.log(error);
    })()
</script>
!(async()=>{
    try{
        const result1 = await sendAjax('https://api.xxx01',{page:1},1)
        console.log('第1次请求成功',result1);
        const result2 = await sendAjax('https://api.xxx02',{page:2},2)
        console.log('第2次请求成功',result2);
        const result3 = await sendAjax('https://api.xxx03',{page:3},3)
        console.log('第3次请求成功',result3);
    } catch (error){
        console.log(error);
})()

1. async修饰的函数:

  • 函数的返回值为promise对象
  • Promise实例的结果由async函数执行的返回值决定

2. await表达式:

  • await右侧的表达式一般为Promise实例对象,但也可以是其它的值
  • 如果表达式是promise对象,await后的返回值是promise成功的值
  • 如果表达式是其它值,直接将此值作为await的返回值

3. 注意:

  • await必须写在async函数中,但async函数中可以没有await
  • 如果await的promise失败了,就会抛出异常,需要通过try...catch来捕获处理