Promise介绍与微任务案例
简介
Promise
是异步编程的一种解决方案,比传统的解决方案回调函数和事件
更合理和更强大,且其链式调用的方式,避免了回调地狱,更是广受大家喜爱
Promise
是一个对象,更是一个容器,里面保存着未来才会发生的事件(异步事件),其有两个特点:
- 其里面保存着三个状态(请求过接口的一般都会了解到):
进行中 pending
(即请求中)、已成功 fulfilled
、已失败 rejected
,即:默认创建完Promise
后,状态为pending
,resolve
之后为fulfilled
,reject
之后为rejected
- 此外,后续发生改变后(
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
方法,发现为 promise0
为 pending
,因此 then
不能执行,将其任务追加到 promise0
任务所在任务尾部,可以将该任务看做一个链表,需要一个个执行
2、由于当前任务中没有执行完毕,继续往后执行,下面的 Promise.resove
与上面一样,不多讲,然后走到then,promise1
被入队,然后走下一个then
,发现promise1
为 pending
,然后依次入队,promise1
、promise2
、promise3
、promise5
、promise6
,其也可以理解为链表的形式,第一轮任务执行完毕,此时队列任务可以表示为 promise0、promise1
(可以理解为,他们里面带的任务在其后面被打包入队了)
3、第一轮任务执行完毕后,出队,然后开始执行队列队首任务promise0
,随后 console.log(0)
执行,然后promise0
执行 resolve
,检查发现,resolve
的值 promise4
为 thenable
类型数据(包含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