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">内部直接使用。
相关推荐
Boilermaker199217 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子28 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102443 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构