用自己的话讲有趣的知识。
大家好,我是梅巴哥er
。本篇讲一些回调函数、promise相关的知识。
以后遇到promise相关的知识,也会在这篇博客里更新。涉及到的讲解,都是个人的理解,如有不当之处,欢迎评论区指出。会及时回复和更正。
参考资料
说到promise,就不得不先说说回调函数。
啥是回调函数
- 顾名思义,就是(先执行别的代码,再)回头调用的函数。
// 先看个栗子
function a(m) {
console.log(m)
}
function b(callback) {
var n = 1
callback(n)
}
b(a) // 输出 1
// 可以看出:
// 回调函数a被当做实参传入函数b
// 在执行函数b时,回调函数a被调用
上个栗子是同步执行的函数,即a在b调用的时候,是立即执行的,
不需要等待。
// 那么,我们再来看个栗子
function callback() {
console.log(1)
}
setTimeout(callback, 1000)
// 先执行外层的setTimeout函数,然后调用callback函数。
// 这里的callback就是一个回调函数。
// 回调函数在另一个函数里充当参数
很明显,这个栗子是异步回调,在执行callback回调函数前,是需要等待的。
也就是说,执行回调函数有同步的,也有异步的。
在实际应用中,最多的是在异步中的应用。
回调函数有啥用
从上面的例子可以看出,
- 回调函数是既有同步执行,也有异步执行
- 异步执行时,不会堵塞执行通道。(让别人先执行,自己再异步执行,是不是很有礼貌)
- 可以优化加载性能,比如懒加载图片等。
回调地狱又是怎么回事呢
我们先来看个例子:
function a(num, callback) {
setTimeout(() => {
console.log(num)
callback() // 回调函数
}, 1000)
}
a(1, function () { // 无限调用
a(2, function() {
a(3, function () {
a(4, function () {
a(5, function () {
console.log('多少层了')
})
})
})
})
})
// 可以无限写下去。。
从上面例子可以看出,
- 函数一直在调用回调函数,一直进行到第N层。
- 太复杂,看多了容易晕。
- 不便于代码的修改和维护。
- 当回调函数复杂时,层次感较差,自己写的过段时间再看可能都看不懂。
我们中国有句老话,某个人要是做了罪大恶极的事情,都会被打入18层地狱!
看上面栗子的函数,一层一层往下去,像不像地狱???
所以,这种回调,就叫回调地狱
!
然而,我们平时的应用,又经常需要异步调用,甚至还套娃调用,该怎么办呢?
ES6的promise解决回调地狱
现在,我们把上面的代码改造一下:
function a(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(num)
resolve() // 状态成功,执行then里面的函数
// reject() // 状态失败,执行catch里面的函数
}, 1000)
})
}
a(1)
.then(() => a(2)) // then函数里返回的仍然是一个promise对象
.then(() => a(3))
.then(() => a(4))
.then(() => a(5))
.then(() => console.log('多少层了'))
.catch(error => console.log(error))
// resolve取代了回调函数
// 当状态成功时,then指向的是回调函数,一个新的promise对象
现在再看,是不是更清晰了?
你看着一层一层的,很清晰美观,跟千层饼似的,看着就流口水…真香!
例子带来的疑问
看了上面的栗子,你是不是想问:
这是啥意思啊?
那个promise是啥?
怎么还有一堆.then()是干啥的?
resolve、reject怎么还有状态?
用了promise怎么就突然一层一层的清晰了啊?
先从Promise是什么
说起
从上面的例子,我们不难看出:
- promise是一个函数返回的对象
// 如上面的栗子
function a(num) {
return new Promise((resolve, reject) => {
...
}
}
// 函数a返回(return)了一个对象new Promise()
// 以后我们想用Promise了,就返回或声明一个Promise即可
// Promise通过.then绑定了回调函数(then里面就是回调函数了)
// resolve状态下,执行了then里面的回调函数
// 和回调地狱里的栗子a(num, callback){...}相比,
// Promise栗子的a函数的形参里,
// 并没有写callback回调函数了,因为回调函数已经通过promise绑定了
// .then可以添加多个,按照顺序依次执行,直至回调结束
// 这种多个then(和.catch)的形式,叫链式调用
我们再来看个栗子:
let promise = new Promise((resolve, reject) => {
console.log(1)
// resolve() // 这个状态才会执行then
reject() // 这个状态才会执行catch
// catch后的then,不管什么状态,都会执行
})
promise
.then(() => console.log(2))
.catch(() => console.log(3))
.then(() => console.log(4))
// 输出:1 3 4
// 可以看出:
// 当状态是reject时,会执行.catch里的函数。
// .catch前的.then不会执行
// 而.catch后面的.then不受影响,会继续执行
上面一直说的状态成功失败是啥意思呢?
这只是个人的一种表述习惯。
其实,这也是在说Promise的状态。
Promise有三种状态且必定处于其中一种状态:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
当操作成功时(异步任务顺利完成且返回结果值):
就会执行.then的回调函数(即调用 resolve 函数)
当操作失败时(异步任务失败且返回失败原因):
就会执行.catch的函数(即调用reject 函数)
举两个栗子,加深理解
// example1
let pro = new Promise((resolve, reject) => {
setTimeout(
() => resolve('成功') // 异步代码执行成功,调用resolve函数
, 1000)
})
pro
.then(message => console.log('good!' + message + '了!'))
// message的值,是上面调用resolve(..)函数传入的值
// 相当于这样:
// function resolve(message) {console.log('good!' + message + '了!')}
// 这个值不一定是字符串,还可以是别的。
// 比如 resolve(data)
// example2
let pro = new Promise((resolve, reject) => {
setTimeout(
() => reject('failed...') // 异步代码执行失败,调用reject函数
, 1000)
})
pro
.then(message => console.log('good!' + message + '了!'))
.catch(error => console.log(error))
.then(() => console.log('end!'))
// 输出:
// failed... end!
// reject('failed...')就相当于:
// function reject(error) {console.log(error)}
// 再调用reject函数: reject('failed...')
说说async/await
既然说了ES6的Promise,那就不得不提ES7的async/await。因为async就是为了简化Promise而出现的。
为了更好的理解,我现在对Promise例子的example1代码进行改造:
// example1改造后
let pro= () => new Promise((resolve, reject) => {
setTimeout(
() => resolve('成功') // 异步代码执行成功,调用resolve函数
, 1000)
})
async function asyncExample() {
console.log(1)
const result = await pro()
console.log(result)
}
asyncExample()
// 输出: 1 成功
将改造前后的代码做对比,问题出现了
- 改造前的.then去哪里啦?
- 改造前是
let pro = new Promise(..)
,改造后怎么变成let pro= () => new Promise(..)
? - 改造后看着更简洁了,看着像是变成了同步代码,async/await 有什么讲究吗?
问题分析和async讲解
从例子的改造,我们可以得知:
- async是一种声明式函数,即在普通函数前加上async。
async function pro(){..}
- async/await常常同时出现,但不是必须。await可以是0个,也可以是N多个。
- await后面跟的是一个Promise函数,如
await pro()
- await pro()生成的是一个promise对象。如
const result = await pro()
,这里的result就是一个promise对象 - await必须放在async函数里面,才能执行。放在外面对报错。
- async函数一定会返回一个promise对象
await pro()
就相当于.then((param) => {return param})
promse和async的关系与区别
- 大多数async函数也可以使用Promises编写
- 在错误处理方面,async函数更容易捕获异常错误
- async函数是在Promise函数的基础上改造的
- async会返回一个存储了数据的promise对象
- async能更直观的获取数据
以上。