Promise详解

初识Promise

  1. 为什么需要promise?

问题: 异步必须通过回调函数来返回结果,回调函数一多就会出现"回调地狱"问题。

  1. Promise
  • Promise 可以帮助我们解决异步中的回调函数的问题。
  • Promise 就是一个用来存储数据的容器。

它拥有着一套特殊的存取数据的方式,这个方式使得它里边可以存储异步调用的结果

在调试控制台中输出对象,[[]]表示这个属性是私有属性,无法访问

[[Prototype]]: Promise

[[PromiseState]]: "fulfilled"

[[PromiseResult]]: 1

创建对象

  • 创建 Promise 时,构造函数中需要一个函数作为参数

  • Promise 构造函数的回调函数,它会在创建 Promise 时调用,调用时会有两个参数传递 进去

  • resolve 和 reject 是两个函数,通过这两个函数可以向 Promise 中存储数据

    • resolve 在执行正常时存储数据
    • reject 在执行错误时存储数据
  • 通过函数来向 Promise 中添加数据,好处就是可以用来添加异步调用的数据

js 复制代码
const promise=new Promise((resolve,reject)=>{
    resolve("通过resolve存储数据")
    //reject("通过reject存储数据")
})
console.log(promise)
// 大概的底层的实现
/*class Promise{
    function resolve(data){
        this.#PromiseResult=data
    } 
    function reject(data){
        this.#PromiseResult=data
    }
    constructor(cb){
        cb(resolve,reject)
    }
}*/

私有属性

  • PromiseResult用来存储数据
  • PromiseState 记录Promise的状态

实例方法

  • then
    • 可以通过 Promise 的实例方法 then 来读取 Promise 中存储的数据。
    • then 需要两个回调函数作为参数,回调函数用来获取 Promise 中的数据。
    • 通过 resolve 存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码。
    • 通过 reject 存储的数据或者出现异常时,会调用第二个函数返回,可以在第二个函数中编写处理异常的代码。
js 复制代码
const promise=new Promise((resolve,reject)=>{
  resolve("通过resolve存储数据")
  //reject("通过reject存储数据")
})
promise.then((result)=>{
  console.log(1,result)
},(reason)=>{
  console.log(2,reason)
})
//大概的底层实现
/*class Promise{
  then(f1,f2){
      if(this.#PromiseState===fulfilled){
          f1(this.#PromiseResult)
      }else if(this.#PromiseState===reject){
          f2(this.#PromiseResult)
      }
  }
}*/
  • catch

    • catch() 用法和 then 类似,但是只需要一个回调函数作为参数。
    • catch()中的回调函数只会在 Promise 被拒绝时才调用。
    • catch() 相当于 then(null, reason => {})。
    • catch() 就是一个专门处理 Promise 异常的方法。
    • 当Promise出现异常时,而整个调用链中没有出现catch,则异常会向外抛出(调用链前面的异常会抛给后面的处理,所以我们一般把catch放到最后)。
  • finally

    • 无论是正常存储数据还是出现异常了,finally 总会执行。
    • 但是 finally 的回调函数中不会接收到数据。
    • finally()通常用来编写一些无论成功与否都要执行代码。

状态

Promise对象有三种状态:

  • pending(进行中)、fulfilled(已成功)、rejected(已失败)。
  • 一个promise对象只能改变一次状态,在PromiseResult中也只能赋一次值。

原理

  • 当 Promise 创建时,PromiseState初始值为 pending。
  • 当通过resolve存储数据时, PromiseState 变为fulfilled(完成) PromiseResult变为存储的数据。
  • 当通过reject存储数据或出错时 PromiseState 变为rejected(拒绝,出错了)PromiseResult变为存储的数据或异常对象。
  • 当我们通过then读取数据时,相当于为Promise设置了回调函数。
  • 如果PromiseState变为fulfilled,则调用then的第一个回调函数来返回数据。
  • 如果PromiseState变为rejected,则调用then的第二个回调函数来返回数据。

链式调用

text 复制代码
promise中的
    then (return new Promise())
    catch
    finally
        - finally的返回值,不会存储到新的Promise中

这三个方法都会返回一个新的Promise,Promise中会存储回调函数的返回值

后边的方法(then和catch)读取的上一步的执行结果
如果上一步的执行结果不是当前想要的结果,则跳过当前的方法
  • then()方法的返回值为一个Promise对象。
  • then中不论调用完成的回调还是失败的回调,都会返回完成的Promise对象,并将回调的返回值封装到该Promise对象的PromiseResult,除非回调函数中抛出异常。
  • 若方法中的回调函数的返回值是Promise对象,则then方法的返回值为该Promise对象。
  • 若不是Promise对象,则将返回值封装到返回的Promise对象中的PromiseResult中。
js 复制代码
  const p = new Promise((resolve, reject) => {
    resolve("数据1")
  })
  const p1 = p.then((result) => {
    console.log(result)
    return "数据2"
  }, null)

  const p2 = p1.then((result) => {
    console.log(result)
  }, null)

  p2.then((result) => {
    console.log(result)
  })
  /*
  数据1
  数据2
  undefined*/

静态方法

创建即时对象:

  • Promise.resolve() 创建一个立即完成的Promise
  • Promise.reject() 创建一个立即拒绝的Promise
js 复制代码
const p = Promise.resolve(10)//相当于创建对象
console.log(p)
//等价于
// new Promise((resolve, reject) => {
//     resolve(10)
// })

all

  • Promise.all([...]) 同时返回多个Promise的执行结果(执行结果的数组)其中有一个报错,就返回错误。
  • Promise.allSettled([...]) 同时返回多个Promise的执行结果(无论成功或失败) 成功:{status: 'fulfilled', value: 579} 失败:{status: 'rejected', reason: '哈哈'}。
js 复制代码
Promise.all([
    sum(123, 123),
    sum(2, 555),
    sum(43, 56)
]).then(r => {
    console.log(r)
})//[246, 557, 99]
scss 复制代码
Promise.allSettled([
    sum(123, 123),
    sum(2, 555),
    Promise.reject("哈哈"),
    sum(43, 56)
]).then(r => {
    console.log(r)
})

最快

  • Promise.race([...])返回执行最快的Promise(不考虑对错)。
  • Promise.any([...]) 返回执行最快的完成的Promise(考虑对错)。

如果any的参数数组中全为拒绝的Promise对象,则只能报错。

js 复制代码
Promise.race([
    Promise.reject(1111),
    sum(123, 456),
    sum(5, 6),
    sum(33, 44)
]).then(r => {
    console.log(r)
}).catch(r => {
    console.log("错误")
})//错误
js 复制代码
Promise.any([
    Promise.reject(1111),
    sum(123, 456),
    sum(5, 6),
    sum(33, 44)
]).then(r => {
    console.log(r)
}).catch(r => {
    console.log("错误")
})//579

事件机制

  • JS是单线程的,它的运行时基于事件循环机制(event loop)。
    • 调用栈放的是要执行的代码。
    • 任务队列的是将要执行的代码。
    • 当调用栈中的代码执行完毕后,队列中的代码才会按照顺序依次进入到栈中执行。
    • 在JS中任务队列有两种。
      • 宏任务队列 (大部分代码都去宏任务队列中去排队),如:定时器、dom绑定事件、ajax。
      • 微任务队列 (Promise的回调函数(then、catch、finally))。
  • 整个流程

① 执行调用栈中的代码 --> ② 执行微任务队列中的所有任务 --> ③ 执行宏任务队列中的所有任务

Promise的执行原理

  • Promise在执行,then就相当于给Promise绑定了回调函数。
  • 当Promise的状态从pending 变为 fulfilled/rejected时,then的回调函数会被放入到任务队列中。

queueMicrotask

js 复制代码
// queueMicrotask(回调函数) 用来向微任务队列中添加一个任务
setTimeout(() => {
    console.log(1)
}, 0)      //宏任务
queueMicrotask(() => console.log(2))  // 微任务,队列中先进先出,执行按顺序
Promise.resolve().then(() => console.log(3))  // 微任务
console.log(4)   // 同步栈
// 4 2 3 1

是否能看出以下程序输出顺序?

js 复制代码
console.log(1);

setTimeout(() => console.log(2));

Promise.resolve().then(() => console.log(3));

Promise.resolve().then(() => setTimeout(() => console.log(4)));
//当它执行时才会把4放进宏任务队列

Promise.resolve().then(() => console.log(5));

setTimeout(() => console.log(6));

console.log(7);


//1735264

手写Promise

js 复制代码
//起步构建
// 1.用类创建Promise,类中需要有个执行器executor
// 2.执行者中发生错误,交给异常状态处理
// 3.执行者中状态只能触发一次,状态触发一次之后,不能修改状态
// 4.执行者中的this,由调用执行者的作用域决定,因此我们需要将执行者中的this绑定为我们创建的Promise对象。
// 5.在构造函数中需要为Promise对象创建status和value记录Promise的状态和传值。
​
class MyPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
​
    constructor(executor) {
        this.status = MyPromise.PENDING;
        this.value = null;
        this.callbacks = [];
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
​
    resolve(value) {
        if (this.status == MyPromise.PENDING) {
            this.status = MyPromise.FULFILLED;
            this.value = value
            setTimeout(() => {
                this.callbacks.map(item => {
                    item.onFulfilled(this.value);
                })
            })
​
        }
    }
​
    reject(reason) {
        if (this.status == MyPromise.PENDING) {
            this.status = MyPromise.REJECTED;
            this.value = reason
            setTimeout(() => {
                this.callbacks.map(item => {
                    item.onRejected(this.value);
                })
            })
​
        }
    }
​
    //开始写then方法
    //1.then接收2个参数,一个成功回调函数,一个失败回调函数
    //2.then中发生错误,状态为rejected,交给下一个then处理
    //3.then返回的也是一个Promise
    //4.then的参数值可以为空,可以进行传值穿透
    //5.then中的方法是异步执行的
    //6.then需要等promise的状态改变后,才执行,并且异步执行
    //7.then是可以链式操作的
    //8.then的onFulfilled可以用来返回Promise对象,并且then的状态将以这个Promise为准
    //9.then的默认状态是成功的,上一个Promise对象的状态不会影响下一个then的状态
    //10.then返回的promise对象不是then相同的promise
    then(onFulfilled, onRejected) {
        if (typeof onFulfilled != 'function') {
            onFulfilled = value => value
        }
​
        if (typeof onRejected != 'function') {
            onRejected = reason => reason
        }
​
        let promise = new MyPromise((resolve, reject) => {
            if (this.status == MyPromise.FULFILLED) {
                setTimeout(() => {
                    this.parse(promise, onFulfilled(this.value), resolve, reject)
                });
​
            }
​
            if (this.status == MyPromise.REJECTED) {
                setTimeout(() => {
                    this.parse(promise, onRejected(this.value), resolve, reject)
                })
​
            }
​
            if (this.status == MyPromise.PENDING) {
                this.callbacks.push({
                    onFulfilled: value => {
                        this.parse(promise, onFulfilled(value), resolve, reject)
                    },
                    onRejected: reason => {
                        this.parse(promise, onRejected(reason), resolve, reject)
                    }
                });
​
            }
        })
        return promise
    }
​
    //整理冗余代码
    parse(promise, result, resolve, reject) {
        if (promise == result) {
            throw new TypeError('Chaining cycle detected for promise')
        }
        try {
            if (result instanceof MyPromise) {
                result.then(resolve, reject)
            } else {
                resolve(result)
            }
        } catch (error) {
            reject(error)
        }
    }
​
    //Promise的静态方法,resolve
    static resolve(value) {
        return new MyPromise((resolve, reject) => {
            if (value instanceof MyPromise) {
                value.then(resolve, reject)
            } else {
                resolve(value)
            }
        })
    }
​
    //Promise的静态方法,reject
    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason)
        })
    }
​
    //Promise的静态方法,all
    static all(promises) {
        let values = [];
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                if (promise.status == MyPromise.FULFILLED) {
                    values.push(promise.value)
                } else if (promise.status == MyPromise.REJECTED) {
                    reject(promise.value)
                }
                if (values.length == promises.length) {
                    resolve(values)
                }
            });
        })
    }
​
    //Promise的静态方法,race
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(promise => {
                promise.then(value => {
                    resolve(value)
                })
            });
        })
    }
​
    //Promise的静态方法,race
    catch (onRejected) {
        return this.then(null, onRejected)
    }
}

async与await

  • 通过async可以来创建一个异步函数。

  • 异步函数的返回值会自动封装到一个Promise中返回。

js 复制代码
async function fn() {
    return 10;
}
let result = fn()
console.log(result)//Promise对象
result.then((result) => {
    console.log(result)//10
})
/*等价于:
function fn(){
    return new Promise((reslove,reject)=>{
        reslove(10)
    })
}*/

await

  • Promise解决了异步调用中回调函数问题,虽然通过链式调用解决了回调地狱,但是链式调用太多以后还是不好看。
    • 我们更希望以同步的方式去调用异步的代码。
    • 当我们通过await去调用异步函数时,它会暂停代码的运行,直到异步代码执行有结果。(PromiseResult有值)时(不会影响执行栈中的同步代码),才会将结果(PromiseResult)返回。

注意:

  • await只能用于 async声明的异步函数中,或es模块的顶级作用域中。
  • await阻塞的只是异步函数内部的代码,不会影响外部代码。
  • 通过await调用异步代码时,需要通过try-catch来处理异常。
  • 当我们使用await调用函数后,当前函数后边的所有代码会在当前函数执行完毕后,被放入到微任务队里中【注意是执行完毕后,这也是await实现代码异步的关键】。
js 复制代码
async function fn4() {
    console.log(1)
    await console.log(2) // await调用的是普通函数,不是异步函数,所以是在执行栈中完成的
    await console.log(3)
    console.log(4)
}
//等价于:
function fn5() {
    return new Promise(resolve => {
        console.log(1)
        // 加了await
        console.log(2)
        resolve()
    }).then(r => {
        console.log(3)
    }).then(r=>{
        console.log(4)
    })
}
fn5()
console.log(5)
// 12534

使用场景

  • async函数内;
  • .mjs文件内;
  • 网页里<script type="module">内部直接使用。
相关推荐
GIS程序媛—椰子24 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00130 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端33 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x36 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100937 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习