likes
comments
collection
share

Promise介绍与微任务案例

作者站长头像
站长
· 阅读数 44

简介

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大,且其链式调用的方式,避免了回调地狱,更是广受大家喜爱

Promise是一个对象,更是一个容器,里面保存着未来才会发生的事件(异步事件),其有两个特点:

  1. 其里面保存着三个状态(请求过接口的一般都会了解到):进行中 pending(即请求中)、已成功 fulfilled已失败 rejected,即:默认创建完 Promise 后,状态为pendingresolve 之后为 fulfilledreject之后为 rejected
  2. 此外,后续发生改变后(fulfilled、rejected)状态不会再被更改(这是需要注意的地方,即resolve后,变成fulfilled后,再执行reject也不会再变成rejected了)

ps:Promise 一旦创建创建后会立即执行,且无法取消,内部出现的错误,不会反馈到外面,但可以通过 .catch 捕获到

基本使用

//创建一个promise
function testPromise() {
    return new Promise((resolve, reject) => {
        if (条件) {
            resolve('正常运行')
        }else {
            reject('出现错误了')
        }
    })
}

//使用promise,获取 promise 会立即执行 promise,随后,其状态从 pending 变为其他两个中的一个
//且.catch里面除了能获取到 reject后的结果,还能捕获前面出现的错误(promise、then中的错误)
testPromise().then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

.then

此外一些人可能也会看到下面写法,也是没错的,then 函数中,一共有两个参数: resolve 回调、reject 回调,只不过我们会省略

//案例一
testPromise().then(res => {
    console.log(res)
}, err => {
    console.log(123, err)
})

当实现 then 中的 reject 回调后,后面的 .catch 便不会再走了,其为一个不正确的使用方法

//此时.catch不会走了,使用不当
testPromise().then(res => {
    console.log(res)
}, err => {
    console.log(123, err)
}).catch(err => {
    console.log(err)
})

.then的链式使用

其主要是为了解决多个需要顺序执行的 promise,可以避免其陷入回调地狱,让代码看起来更加优雅,看起来更加通俗易懂

下面给出链式调用的案例,只需要在 .then 里面返回下一个要执行的 promise,便可以在下一个 .then 里面获取到我们返回的 promise 的结果了

ps.then 里面返回的内容可以是 promise,也可以是一个非 promise 对象,其均会加工成一个新的 promise 返回,用于后续调用

function promise1() {
    return new Promise((resolve, reject) => {
        if (new Date().getTime() % 10 > 5) {
            resolve('1正常运行')
        }else {
            reject('1出现错误了')
        }
    })
}

function promise2() {
    return new Promise((resolve, reject) => {
        if (new Date().getTime() % 10 < 5) {
            resolve('2正常运行')
        }else {
            reject('2出现错误了')
        }
    })
}

function testPromiseNext() {
    //可以链式处理promise回调问题,且合并错误,避免陷入回调地狱
    //缺点是需要单独处理的错误则不太好用,需要随机应变
    promise1().then(res => {
        //promise1执行完了,开始执行promise2
        return promise2()
    }).then(res => {
        //promise2也执行完毕了
        console.log('res:', res)
    }).catch(err => {
        //promise1和promise2有错误都会到这里来
        console.log('catch:', err)
    })
}

promise 执行的时候会生成微任务来保证内部任务有序进行,后续会通过大家讨论比较多的案例,从而间接了解解其任务怎么产生和执行的,会以什么顺序执行下去

.catch

.catch 是用来代替 .then(null, rejection).then(undefined, rejection)

此外,其不但能捕获 reject 结果,还能捕获里面发生的其他异常,也是最推荐的写法

testPromise().then(res => {
    console.log(res)
    throw new Error('我是.then里面报的错误')
}).catch(err => {
    //我能捕获到前面所有promise和.then里面的错误
    //当然如果是testPromise前面还有一个非promise的调用,其错误就捕获不到了
    console.log(err)
})

.finally

.finall 可以解决在 promise 执行完毕后,继续做后续的事情,避免再封装一层了(例如:一次期末考试,无论结果及格不及格,有没有参加考试,我都要记录到本本上😂)

testPromise().then(res => {
    //成功回调
}).catch(err => {
    //失败回调
}).finally(err => {
    console.log('不管成功失败都到我这里')
})

Promise.all 、Promise.allSettled、Promise.race、Promise.any

先准备两个基础 promise 案例

function promise11() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(10)
        }, 500);
    })
}

function promise12() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(11)
        }, 1000);
    })
}

Promise.all 看名字就知道,为一组 promise 全部成功后的回调,成功后回调为结果组成的数组,一旦有一个失败,则直接走 .catch,标识着失败

Promise.all([promise11(), promise12()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

Promise.allSettled 有都执行完毕的意思,与Promise.all 类似,只不过其不关注执行的结果成败,只关注是否执行完毕,即:所有的 promise 状态都 不为 pending 时,返回执行后的 promise 数组(即不管成功失败,我只要知道你们都执行完了,都继续下一步),此时甚至没必要使用 .catch 了

Promise.allSettled([promise1(), promise2()]).then(res => {
    console.log('allSettled', res)
})

Promise.race 名字中带有赛跑的意思,意味着只记录一组 promise 中最快的那个,无论成败,并且将最快 promise 结果返回,其成功走 .then,失败走 .catch

Promise.race([promise11(), promise12()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

Promise.any 内部出现任何一个成功(fulfilled)的 promise,则其状态调整为成功(fullilled),返回其成功的结果;如果内部某个先完成,但为失败(rejected)不更新其状态,即不返回结果,直到所有的 promise 均失败(rejected),此 promise 才会标记为失败(rejected)

简而言之:其内部所有的 promise 失败才会走失败,有一个成功则直接返回结果

Promise.any([promise1(), promise2()]).then(res => {
    console.log('any', res)
}).catch(err => {
    console.log('anyerr', err)
})

Promise.resolve

快速生成一个 Promise 对象,状态为 fulfilled,为一个工厂构建方法,使用简单

Promise.resolve('成功了')
// 相当于
new Promise(resolve => resolve('成功了'))

其可以传递从参数,也可以不传递参数

1.回调函数不传递参数,一般用不到回调参数的时候使用,仅仅用来走成功回调

2.传递普通对象(例如字符串),则包装成一个 promise 返回,因此支持在 .then 中 获取传递的内容

3.传递 promise 对象,原封不对返回

4.传递 thenable 对象(带有 then 方法),直接执行 then 方法,并返回一个新的 promise 对象

Promise.reject

与 resolve 类似,快速生成一个 Promise 对象,状态为 rejected,为一个工厂构建方法,使用简单

Promise.reject('失败了')
// 相当于
new Promise(reject => reject('失败了'))

Promise.try

这个平时用的比较少,一旦使用,会感觉到在一些场景,用的非常舒服,因此了解是必要的,其参数为函数

场景1

需要执行一个 外部方法,但不知道是同步还是异步(例如:带有async,但不一定有 await),想用 promise 来处理他的结果,但最好是保持原来的执行方式,即: 同步方法立即执行,异步方法异步执行,其可以这么写

const f = () => console.log('我是一个未知函数')

//使用匿名函数
(async () => f())().then(res => {
    ...
}).catch(err => {
    ...
})

但看起来不优雅,我们使用 Promise.try 改进一下,看一下是不是很简洁易懂了

const f = () => console.log('我是一个未知函数')

//使用 try 调用函数,传入函数
Promise.try(f).then(res => {
    ...
}).catch(err => {
    ...
})

场景2

有下面一个方法,通过调用一个方法获取一个我们需要的 promise,但获取 promise 之前,有概率也会出现其他错误,需要统一处理

//我们从本地数据库中获取一个用户id,结果返回一个 promise
Database.share.user.get('user_id')
    .then(...)
    .catch(...)

上面案例,我们需要注意的是 我们打开数据库的过程可能会失败,但这个错误由于不是 promise,却不会被后面的 catch 捕获,会导致错误无法被捕获

我们使用 try...catch 改进后变成这样,但不够优雅,且 .catch 还要写两套代码

try {
    Database.share.user.get('user_id')
        .then(...)
        .catch(...)
}.catch(err) {
    ...
}

显然不够优雅,再使用 Promise.try 改进一下,这样就可以统一捕获了

//由于这段代表并不是函数,我们快速生成一个匿名函数即可
Promise.try(() => Database.share.user.get('user_id'))
    .then(...)
    .catch(...)

promise 微任务案例

之前看到过一个 promise 的微任务案例,讨论的很热烈,需要了解的 点击这里,讲的是 Promise.then 中 返回的 Promise 的 疑问与探讨,题目如下所示,可以结合前面讲的,猜一下答案

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4)
}).then(res => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
})


//打印结果为 
0 1 2 3 4 5 6

可能有些人会疑惑,下面就以自己的理解简述述这个过程把,如果感觉有疑问可以点进去上面链接,查看其他人更加详细的介绍

执行过程

ps:说明前,先介绍一下队列,为一个先进先出的数据结构,入队为队尾增加任务,出队为队首减少任务

1、首先执行第一个任务上面的语句,为Promise.resove,其默认返回到为 Promise{undefined},继续调用 then 方法,发现Promise{undefined} fulfilled状态,直接入队 console.log(0);return Promise.resolve(4)任务,返回新的promise0(log0里面的 取名 promise0)然后继续调用后面的 then方法,发现为 promise0pending,因此 then不能执行,将其任务追加到 promise0任务所在任务尾部,可以将该任务看做一个链表,需要一个个执行

2、由于当前任务中没有执行完毕,继续往后执行,下面的 Promise.resove与上面一样,不多讲,然后走到then,promise1被入队,然后走下一个then,发现promise1pending ,然后依次入队,promise1promise2promise3promise5promise6,其也可以理解为链表的形式,第一轮任务执行完毕,此时队列任务可以表示为 promise0、promise1(可以理解为,他们里面带的任务在其后面被打包入队了)

3、第一轮任务执行完毕后,出队,然后开始执行队列队首任务promise0,随后 console.log(0) 执行,然后promise0 执行 resolve,检查发现,resolve 的值 promise4thenable 类型数据(包含then方法),根据标准,不管该 thenable数据是否执行完毕,都要入队一个包含返回值.then的任务,即入队新任务 promise4.then(没有会增加返回内容的默认方法,有就调用给的出),后面的任务也会被顺势打包到其后面(前面说的链表),此时任务执行完毕,出队 promise0,此时队列为 promise1、promise4.then

4、执行 promise1,执行console.log(1),然后 resolve 返回 undefined,没什么异常,入队后面的 promise2,出队 promise1,此时队列为 promise4.then、promise2

5、执行 promise4.then,这里代码没有直接给出 .then,因此用的默认的.then,执行后返回,因为其使用的是 promise0 的 resolve,因此需要入队 promise0-resolve 的任务,将值带出去为尾部的 res 使用,然后出队 promise4.then,此时队列为 promise2、promise0-resolve

6、执行 promise2,执行console.log(2),随后入队 promise3,出队 promise2,此时队列为 promise0-resolve、promise3

7、执行 promise0-resolve,此时外部无法感知,其执行完毕,后面的 promiseres入队,promise0-resolve出队,队列此时为 promise3、promiseres

8、执行 promise3,执行console.log(3),随后入队 promise5,出队 promise3,此时队列为 promiseres、promise5

9、执行 promiseres,执行console.log(res),打印4,出队 promise3,此时队列为 promise5

10、执行 promise5,执行console.log(5),出队 promise6,此时队列为 promise6

11、执行 promise6,执行console.log(5)

因此打印结果为 0、1、2、3、4、5、6

最后

promise 也是一个易用难懂的一个数据结构,且想理解一些里面稍微繁琐一点的逻辑,也需要一定的数据结构与算法功底,因此打好基础也是很重要的

然后祝大家有所收获!

转载自:https://juejin.cn/post/7239647672460836901
评论
请登录