ES:回调函数、Promise、async/await的一些知识总结(以后遇到问题、会随时更新)

用自己的话讲有趣的知识。


大家好,我是梅巴哥er。本篇讲一些回调函数、promise相关的知识。
以后遇到promise相关的知识,也会在这篇博客里更新。涉及到的讲解,都是个人的理解,如有不当之处,欢迎评论区指出。会及时回复和更正。


参考资料
  • Promise官方文档
  • Promise的使用
  • async函数
  • 阮一峰老师的讲解
  • 我的博文

说到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能更直观的获取数据

以上。

你可能感兴趣的:(js,promise,回调地狱,回调函数)