对写文章这件事已经阁了3个月了,工作太忙很难抽出大块时间来总结写作。现在稍微闲赋些,准备好好对以前的技术做一下总结。为什么要写关于 Promise
呢?有以下 3 点
-
之前面试时候,面试官问
Promise
问的比较多,实在要对它进行一个好好总结了,也是 JavaScript 比较难懂的一个技术点,平时我面试别人时候,也喜欢问Promise
相关的,保证他在工作中能够熟练运用,自己能封装axios
,能控制比较复杂的同步异步流程等... -
这三个月的工作中,自己也总是遇到关于
Promise
的一些运用场景,有时候又比较疑惑一些地方,也是为自己总结一下Promise
,以后在工作中更加运用自如。接下来我从最基础的同步异步谈起,再通过图示谈他的语法,如何自己写一个
Promise
,再谈一下我在工作中遇到的Promise
,最后谈一下 新的语法async
和await
与Promise
的结合和比较 -
网上关于
Promise
的介绍大多数是一些语法的介绍,大多没有结合场景和同步异步相关理解来谈。对于理解Promise
的前世今生还是不够深入,不够具体。
console.log(1)
setTimeout(() => {
console.log(2)
}, 1000)
console.log(3)
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
console.log(1)
setTimeout(() => {
console.log(2)
}, 2000)
console.log(3)
setTimeout(() => {
console.log(4)
}, 1000)
console.log(5)
setTimeout(() => {
console.log(6)
}, 0)
console.log(7)
setTimeout(() => {
console.log(8)
}, 1000)
console.log(9)
这里可以发现几点
- 1,3,5,7,9 是同步代码,6,4,8,2是异步代码,同步先于异步执行
- 单看同步代码,按位置顺序依次执行
- 单看异步代码,不同执行时间,越长越靠后执行。相同执行时间,按位置顺序依次执行
这里我们总结下同步与异步的规律
- 代码中存在异步函数,不管需要时间多久,都要在同步完成后执行
- 同步按位置顺序执行;异步按时间长短执行,同样时间长短时,按位置顺序执行
这是一个怎样的机制,JS引擎又是如何处理的呢?
我找到了一张这样的图
首先来解释一下这个图:这是JS的事件循环,分为3个步骤
- 引擎将同步、异步函数按次序载入到执行栈里面
- 执行栈的将异步函数放入异步线程里面
- 线程根据任务完成时间依次推入任务队列中执行
如何处理异步呢, 最初的方法是使用回调
function fetchSomething() {
console.log('去取货!')
}
function buySomething(callback) {
console.log('没货了!')
setTimeout(() => {
console.log('有货了!')
callback()
}, 1000)
}
buySomething(fetchSomething)
要搞清楚的一点是,回调和异步没有直接的关系,也可以同步回调,也可以异步回调。我们是通过回调这个机制来实现异步的操作,例如
// 这个一个请求的异步函数,来实现异步操作
function getSomePeopleName(params, callback) {
setTimeout(() => {
let data
if (params === 'suo') {
data = 'yue'
}
console.log(callback(data))
}, 1000)
}
console.log(getSomePeopleName('suo', (data) => '我是回调函数:' + data))
回调函数的结果一定是在回调函数里面,如果我是这样一个流程呢?
A -> B -> C -> D
function A(callback) {
console.log('开始执行A')
setTimeout(() => {
callback('A')
}, 500)
}
function B(callback) {
console.log('开始执行B')
setTimeout(() => {
callback('B')
}, 400)
}
function C(callback) {
console.log('开始执行C')
callback('C')
}
function D(callback) {
console.log('开始执行D')
setTimeout(() => {
callback('D')
}, 200)
}
A((a) => {
B((b) => {
C((c) => {
D((d) => {
console.log(a, b, c, d)
})
})
})
})
- 使用回调函数嵌套,回调函数一定在上一个回调之后执行,用于可以控制流程,不会出现异步函数在执行顺序的混乱,即使同步异步函数混合
- 回调函数解构嵌套,代码不够清晰,俗称回调地狱
那么有没有更好的异步操作机制呢?这里我们就要谈到 Promise
了
Promise
是一个构造函数
我们写一个简单的 Promise
看看
new Promise(() => {})
// 有一个参数executor的构造函数
// executor 也是一个函数,具有两个参数 resolve, reject 是两个回调函数,当执行到回调函数时,会执行
const isResolve = true
new Promise((resolve, reject) => {
if (isResolve) {
resolve()
} else {
reject()
}
})
可以看到 Promise
的状态从pending
变成resolved
如果将 isResolve
置为 false
呢?
Promise
状态从 pending
变成 rejected
同时抛出一个异常,并且异常未被捕获,所以我们写Promise时候一定要加上catch 来捕获异常
const isResolve = false
new Promise((resolve, reject) => {
if (isResolve) {
resolve()
} else {
reject('我拒绝你')
}
}).catch((err) => {
console.log('捕获异常', err)
})
现在我们把异常捕获到了,但是神奇的事情发生了,异常状态应该是rejected
,怎么变成 resolved
了呢?
我们或许很纳闷,这个放在后面讨论,我们先看看 then
怎么处理的
const isResolve = true
new Promise((resolve, reject) => {
if (isResolve) {
resolve('通过')
} else {
reject('我拒绝你')
}
}).then((res) => {
console.log('resolve', res)
}, (res) => {
console.log('reject', res)
})
- 如果 前面
resolve()
调用,Promise状态为resolved
,then
则执行第一个回调函数参数 - 如果 前面
reject()
调用,Promise状态为rejected
,then
则执行第二个回调函数参数 - then 执行回调函数之后Promise 的 状态都为 resolved
下面进一步验证下
const isResolve = false
new Promise((resolve, reject) => {
if (isResolve) {
resolve('通过')
} else {
reject('我拒绝你')
}
}).catch(err => {
console.log('我捕获到了', err)
}).then((res) => {
console.log('resolve', res)
}, (res) => {
console.log('reject', res)
})
果然呢,catch
之后,then
还会去执行,并且resolve
的参数为 undefined
问题是现在我们如何控制一个流程呢?Promise
并不能直接给我们进行长流程的分支选择
下面有一个简单流程
const process = [102, 204]
new Promise((resolve, reject) => {
if (process[0] === 101) {
setTimeout(() => {
console.log(101)
resolve(101)
}, 1000)
} else {
setTimeout(() => {
console.log(102)
reject(102)
}, 1000)
}
}).then(() => {
if (process[1] === 201) {
setTimeout(() => {
console.log(201)
}, 500)
} else {
setTimeout(() => {
console.log(202)
}, 500)
}
}, () => {
if (process[1] === 203) {
setTimeout(() => {
console.log(203)
}, 500)
} else {
setTimeout(() => {
console.log(204)
}, 500)
}
}).catch((err) => {
console.log(err)
})
单个Promise
没办法控制长流程,我们怎么将Promise
形成一个控制链呢,需要理解then
的返回在其中起到的作用
promise.then(onFulfilled, onRejected)
-
接收两个参数,
onFulfilled
在promise
的resolved
状态被调用 -
接收两个参数,
onRejected
在promise
的rejected
状态被调用 -
返回值比较复杂,下面用表格列出来
序号 场景 返回的Promise状态改变为 回调函数参数值 1 返回1 个值 resolved 返回值 2 没有返回 resolved undefined 3 抛出错误 rejected 错误 4 resolved的Promise resolved 返回的Promise回调的参数值 5 rejected的Promise rejected 返回的Promise回调的参数值 6 pending的Promise pending 返回的Promise回调的参数值
我们可以看出想要控制then
的后续流程,必须通过这 6 种情况来控制
下面来测试一下 这 6 种情况是否符合我们的预期
- 返回一个值
new Promise((resolve, reject) => {
resolve('通过')
}).then((res) => {
console.log('1', res)
return res // then的返回值
}).then((res) => {
console.log('2', res)
})
分析特点:
这样就比较复杂了,还要考虑暂停的问题,但是根据我们上面测试到的,通过then
的返回值控制流程也没有想象那么难
task([101, 201])
function task(testPath) {
console.log('开始测试', testPath)
new Promise((resolve, reject) => {
console.log('000')
if (101 === testPath[0]) {
resolve('101')
} else {
reject('102')
}
}).then((res) => {
console.log('201', '上一个返回:' + res)
return new Promise(() => {})
}, (res) => {
if (202 === testPath[1]) {
console.log('202', '上一个返回:' + res)
return '202'
} else {
console.log('203', '上一个返回:' + res)
throw '203'
}
}).then((res) => {
console.log('301', '上一个返回:' + res)
return Promise.resolve('301')
}, (res) => {
console.log('302', '上一个返回:' + res)
return Promise.resolve('302')
}).then((res) => {
console.log('401', '上一个返回:' + res)
return Promise.reject('401')
}).catch((err) => {
console.log(err)
})
}
Promise.resolve()
等同用new Promise((resolve, reject) => resolve())
Promise.reject()
等同用new Promise((resolve, reject) => reject())
Promise.all()
// 作为参数promise 数组中,所有promise状态都是resolved才回调then第一个,只要有一个reject就reject
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then((res) => {
console.log('通过', res)
}, (res) => {
console.log('拒绝', res)
})
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)]).then((res) => {
console.log('通过', res)
}, (res) => {
console.log('拒绝', res)
})
Promise.race()
Promise.race([new Promise((resolve, reject) => {
setTimeout(() => {
reject(1)
}, 1001)
}), new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1002)
})]).then((res) => {
console.log('通过', res)
}).catch((res) => {
console.log('拒绝', res)
})
race 相当于竞赛,多个Promise
竞赛,谁先状态变成resolved
和rejected
,谁就执行下面的回调(根据时间来抉择)
总结一下,从同步异步到回调,在到Promise
的语法和应用全部都可以在谷歌浏览器的控制台中输出测试。通过一点点代码的编写和输出,才会让我们思维更清晰,对Promise
的理解更深刻。之后再总结工作中用到的Promise
,以后也会慢慢将async
、await
结合Promise
来谈关于 es6 以后异步相关的新特性。