系列文章:
前言
上篇文章阐述了并发/并行、单线程/多线程、同步/异步等概念,这篇将会分析Promise的江湖地位。
通过本篇文章,你将了解到:
- 为什么需要回调?
- 什么是回调地狱?
- Promise解决了什么问题?
- Promise常用的API
- async和await 如影随形
- Promise的江湖地位
1. 为什么需要回调?
1.1 同步回调
先看个简单的Demo:
function add(a: number, b: number) {
return a + b
}
function reprocess(a: number) {
return a * a
}
function calculate() {
//加法运算
let sum = add(4, 5)
//进行再处理
let result = reprocess(sum)
//输出最终结果
console.log("result:", result)
}
先进行加法运算,再对运算的结果进行处理,最终输出结果。
在reprocess()函数里我们对结果进行了平方,现在想要对它进行除法操作,那么依葫芦画瓢,需要再定义一个函数:
function reprocess2(a: number) {
return a / 2
}
再后来,还需要继续增加其它功能如减法、乘法、取模等运算,那不是要新增不少函数吗?
假设该模块的主要功能是进行加法,至于对加法结果的再加工它并不关心,外界调用者想怎么玩就怎么玩。于是,回调出现了。
我们重新设计一下代码:
//新增函数作为入参
function add(a: number, b: number, callbackFun: (sum: number) => number) {
let sum = a + b
return callbackFun(sum)
}
function calculate() {
//加法运算
let result = add(4, 5, (sum) => {
return sum / sum
})
//输出最终结果
console.log("result:", result)
let result2 = add(6, 8, (sum) => {
return sum * sum - sum / 2
})
//输出最终结果
console.log("result2:", result2)
}
add()函数最后一个入参是函数类型的参数,调用者需要实现这个函数,我们称这个函数为回调函数。于是在calculate()函数里,我们可以针对不同的需求调用add()函数,并通过回调函数实现不同的数据加工逻辑。
calculate()函数和回调函数是在同一线程里执行,并且按照代码书写的先后顺序执行,此时的回调函数是同步回调。
1.2 异步回调
假若add()函数里对数据的加工需要一定的时间,我们用setTimeout模拟一下耗时操作:
//新增函数作为入参
function add(a: number, b: number, callbackFun: (sum: number) => void) {
setTimeout(() => {
let sum = a + b
callbackFun(sum)
})
}
function calculate() {
//加法运算
add(4, 5, (sum) => {
let result = sum / sum
//输出最终结果
console.log("result:", result)//第1个打印
})
console.log("calculate end...")//第2个打印
}
从打印结果看,第2个打印反而比第一个打印先出现,说明第二个打印语句先执行。
calculate()函数执行add()函数的时候,并没有一直等待回调的结果,而是立马执行了第二个打印语句,而当add()函数内部实现执行时,才会执行回调函数,虽然calculate()和回调函数在同一线程执行,但是它们并没有按照代码书写的先后顺序执行,此时的回调函数是异步回调。
1.3 为什么需要它?
回调函数的出现使得代码设计更灵活。
你可能会说:异步回调我还可以理解,毕竟或多或少都会涉及到异步调用,但同步回调不是脱裤子放屁吗?
其实不然,同步回调更多的表现在灵活度上,比如我们遍历一个数组:
const score = [60, 70, 80, 90, 100]
score.forEach((value, index, array) => {
console.log("value:", value, " index:", index)
})
forEach()函数接收的是一个同步回调函数,该函数里可以获取到数组里每一个值,并可以对它进行自定义的逻辑操作。
除了forEach()函数,同步回调还大量地被运用于其它场景。
2. 什么是回调地狱?
先看一段代码:
interface NetCallback {
//错误返回
error: (errMsg: string) => void
//成功返回
succeed: (data: object) => void
}
function fetchNetData(url: string, netCallback: NetCallback) {
//模拟网络耗时
setTimeout(() => {
if (Math.random() > 0.2) {
//成功
netCallback.succeed({
code: 200, msg: 'success'})
} else