异步编程篇
回调函数
function test (callack) {
setTimeout(() => {
let val = 'test'
callback(val)
}, 500)
}
promise
promise.all()
Promise.all([promise 1, promise 2, promise 3]) 等待原则, 是在所有promise都完成后执行, 可以用于处理一些并发的任务,如果Promise实例都进入Fulfilled状态,Promise.all返回的实例才会变成Fulfilled状态并将Promise实实例数组的所有返回值组成一个数组,传给Promise.all返回实例的回调函数;如果有某一个或者多个实例进入rejected状态,Promise.all返回的实例会立即变成Rejected状态并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。
const a = new Promise((resolve, reject) => {
resolve('a')
})
const b = new Promise((resolve, reject) => {
reject('b')
})
const c = new Promise((resolve, reject) => {
reject('c')
})
const d = new Promise((resolve, reject) => {
resolve('d')
})
const result = Promise.all([a, b,c,d]).then((res) => { console.log(res)}).catch((error) => { console.log(error)})
// 如果有一个发生错误,则在catch哪里捕获到第一个错误的接口 然后await 获取的也是b
promise.race()
race() 接受的参数也是一个每项都是 promise的数组,当最先执行完的事件执行完之后,就直接返回该 promise对象的值。如果第一个 promise对象状态变成 resolved,那自身的状态变成了resolved;反之第一个 promise变成 rejected,那自身状态就会变成 rejected.
const a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 1500)
})
const b = new Promise((resolve, reject) => {
setTimeout(() => {
reject('b')
}, 1000);
})
const c = new Promise((resolve, reject) => {
reject('c')
})
const d = new Promise((resolve, reject) => {
resolve('d')
})
Promise.race([a,b]).then((res) => { }).catch((error) => { console.log(error)})
// reject b
Promise.race([a,b,c,d]).then((res) => { }).catch((error) => { console.log(error)}) // resolve b
Promise.race([b,b,c,d]).then((res) => { }).catch((error) => { console.log(error)}) // reject b
promise.finally()
finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
手写promise
兼容同步任务
完成了then的链式调用以后,我们再处理一个前边的细节,然后放出完整代码。上文我们说过,Promise的执行顺序是new Promise -> then()收集回调 -> resolve/reject执行回调
,这一顺序是建立在executor是异步任务的前提上的,如果executor是一个同步任务,那么顺序就会变成new Promise -> resolve/reject执行回调 -> then()收集回调
,resolve的执行跑到then之前去了,为了兼容这种情况,我们给resolve/reject
执行回调的操作包一个setTimeout,让它异步执行。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(exxcutor) { // executor 是一个执行器,进入会立即执行
this._status = PENDING
this._value = undefined
this._resolveQueue = [] // 成功队列
this._rejectQueue = [] // 失败队列
let _resolve = (val) => {
const run = (run) => {
if (this._status !== PENDING) return
this._status = FULFILLED
this._value = val
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
let _reject = (val) => {
const run = (val) => {
if (this._status !== PENDING) return
this._status = REJECTED
this._value = val
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
executor(_resolve, _reject)
}
then(resolveFn, rejectFn) {
// 处理 .then() 方法为空的时候
resolveFn = typeof resolveFn === 'function' : resolveFn : value => value
rejectFn = typeof rejectFn === 'function' : rejectFn : reason => {
throw new Error(reason instanceof Error ? reason.message : reason)
}
return new MyPromse((resolve, reject) => {
const fulfilledFn = val => {
try {
let x = resolveFn(val)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
const rejectedFn = val => {
try {
let x = rejectFn(val)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
case FULFILLED:
fulfilledFn(this._value)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
catch (rejectFn) {
return this.then(undefined, rejectFn )
}
resolve (value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
reject (error) {
return new MyPromise((resolve, reject) => reject(error))
}
finally (callback) {
return this.then(
val => MyPromise.resolve(callback()).then(() => value),
err => MyPromise.resolve(callback()).then(() => throw err)
)
}
all (promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
MyPromise.resolve(p).then(val => {
index++
result[i] = val
if(index === promiseArr.length) {
resolve(result)
}
}, err => {
reject(err)
})
})
})
}
race(promiseArr) {
return new MyPromise((resolve, reject) => {
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value) //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
}
Generator
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。
*/yield
和async/await
看起来其实已经很相似了,它们都提供了暂停执行的功能,但二者又有三点不同:
async/await
自带执行器,不需要手动调用next()就能自动执行下一步async
函数返回值是Promise对象,而Generator返回的是生成器对象await
能够返回Promise的resolve/reject的值
yield 修饰的内容,暂停恢复后会丢失
// 定义函数
function getName(){
return 'ares5k'
}
// 定义生成器
function * generator(){
let name = yield getName()
console.log(name)
}
let genIterator = generator() // 取得迭代器
genIterator.next() // 执行到第一个 yield 后暂停
genIterator.next() // 恢复执行
// 输出:undefined
通过输出结果可以分析大概流程:
(1) 第一次调用 next 方法时,会在 let name = yield getName() 这一行发生暂停,赋值操作是从右到左,此时右侧执行完就已经暂停了,左侧赋值操作还未进行
(2) 第二次调用 next 方法,会恢复函数执行,继续执行 let name = yield getName() 的左侧赋值部分,而此时赋的并不是预期的 getName() 的结果, 而是一个 undefined,因此可以得出结论,yield 修饰的内容,暂停恢复后会丢失
解决 yield 修饰的内容丢失问题:
既然是在恢复时丢失了值,那么只要在恢复执行的那个 next 中,传入 yield 修饰的值即可,而 yield 修饰的内容, 会在前一个 next 暂停时以迭代器结果对象返回( IteratorResult 接口 ),具体实现如下:
// 定义函数
function getName(){
return 'ares5k'
}
// 定义生成器
function * generator(){
let name = yield getName()
console.log(name)
}
let genIterator = generator() // 取得迭代器
const genIteratorResult = genIterator.next() // 取得迭代器结果对象
genIterator.next(genIteratorResult.value) // 恢复执行,并传入 yield 修饰的内容
// 输出:ares5k
解决回调地狱
// 定义函数
let fn = word => {
return new Promise(resolve => {
setTimeout(function () {
console.log(word)
resolve()
}, 500)
})
}
// 业务的逻辑更紧凑,更易读,就像写同步代码一样
function * generator() {
yield fn('a')
yield fn('b')
yield fn('c')
yield fn('d')
yield fn('e')
}
// 按步骤执行各个任务,
const genIterator = generator()
genIterator.next().value.then(()=>{
genIterator.next().value.then(()=>{
genIterator.next().value.then(()=>{
genIterator.next().value.then(()=>{
genIterator.next()
})
})
})
})
可以看出,生成器 + Promise 实现的代码,业务部分的代码,逻辑更紧凑,更易读,就像写同步代码一样,但 是在生成器的调用阶段,就很麻烦,甚至又出现了回调地狱,而且调用多少次 next
都是预先定义好的,不灵活
实现生成器的自动执行
分析上面例子中的回调地狱,我们可以发现它与平时的回调地狱不同,它的回调内部没有很多的业务逻辑代码,仅 仅是调用 next
恢复函数执行而已,了解了这个点,我们就可以对其进行改造:
// 定义函数
let fn = word => {
return new Promise(resolve => {
setTimeout(function () {
console.log(word)
resolve()
}, 500)
})
}
// 业务的逻辑更紧凑,更易读,就像写同步代码一样
function * generator() {
yield fn('a')
yield fn('b')
yield fn('c')
yield fn('d')
yield fn('e')
}
// 生成器的自动执行器
function auto(generator){
function next(data){
const result = genIterator.next(data)
if (result.done) return result.value
result.value.then(function(data){
next(data)
})
}
const genIterator = generator();
next();
}
// 调用生成器的自动执行器
auto(generator);
async await
async
+ await
是比使用生成器或 Promise 更简洁的回调地狱处理方案,也有人把其看作生成器 + Promise + 自执行的语法糖。
<script>
// 场景一:函数执行异常
const withError = async function () {
throw Error
return '异常后的内容不会被执行'
}
withError().catch(data => {
console.log('场景一:函数执行异常时,会返回一个状态为 rejected 的 Promise 对象,异常原因:' + data)
})
// 场景二:未显式返回内容
const nothing = async function () {
}
nothing().then(() => {
console.log('场景二:未显式返回内容时,会返回一个状态为 fulfilled 的 Promise 对象,且处理程序无参')
})
// 场景三:显式返回 Promise 以外的值
const notPromise = async function () {
return '这是原返回值,现被当作参数传入处理程序'
}
notPromise().then(data => {
console.log('场景三:显式返回 Promise 以外的值时,会返回一个状态为 fulfilled 的 Promise 对象,处理程序参数:' + data)
})
// 场景四:显式返回 状态为 fulfilled 的 Promise 对象
const fulfilledPromise = async function () {
return new Promise(resolve => {
resolve('成功')
})
}
fulfilledPromise().then(data => {
console.log('场景四:显式返回 状态为 fulfilled 的 Promise 对象,直接返回该对象')
})
// 场景五:显式返回 状态为 rejected 的 Promise 对象
const rejectedPromise = async function () {
return new Promise((resolve, reject) => {
reject('失败')
})
}
rejectedPromise().catch(data => {
console.log('场景五:显式返回 状态为 rejected 的 Promise 对象')
})
</script>
await 修饰的内容:
① await 修饰 Promise 对象时: Ⅰ. Promise 对象必须调用 resolve 或 reject, 否则该 await 之后的代码不会执行 Ⅱ. await 修饰 Promise 语句之后的代码会一直等待,直到 Promise 调用完 resolve 或 reject 后在执行
② await 修饰非 Promise 对象时: Ⅰ. 不管 await 修饰的代码是否执行完成,之后的代码都会同步执行,不会等待
(2) await 场景列举,进一步熟悉 await 特点
① 场景一:await 标准执行顺序
await 修饰的内容执行 -> 主线程逻辑执行 -> async 函数剩余代码执行 -> setTimeout 执行
<script>
setTimeout(function () {
console.log(1)
})
const noResolveOrReject = async function () {
await console.log(2)
console.log(3)
console.log(4)
}
noResolveOrReject()
console.log(5)
// 输出:2,5,3,4,1
</script>
② 场景二:await 修饰 Promise 对象,后面的代码会一直等待,直到 Promise 调用完 resolve 或 reject 后在执行 如果 await 前是赋值操作,那么 resolve 或 reject 的参数,会赋值给该变量
await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 函数内剩余代码等待调用 resolve 或 reject 后执行
<script>
const noResolveOrReject = async function () {
const value = await new Promise(resolve => {
console.log(1)
setTimeout(() => resolve('会返回给 await 的数据'), 3000)
})
console.log(value)
console.log(2)
console.log(3)
}
noResolveOrReject()
console.log(4)
// 输出:1,4,会返回给 await 的数据,2,3
</script>
③ 场景三:await 修饰的 Promise 未调用 resolve 或 reject 语句,async 函数的剩余代码不会执行
await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 函数内剩余代码不会执行
<script>
setTimeout(function () {
console.log(1)
})
const noResolveOrReject = async function () {
await new Promise(() => console.log(2))
console.log(3)
console.log(4)
}
noResolveOrReject()
console.log(5)
// 输出:2,5,1
</script>
④ 场景四:await 修饰的是非 Promise 对象,不管其是否执行完,后面的代码都会同步执行
await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 剩余代码不会等待 setTimeout 直接执行
<script>
const noResolveOrReject = async function () {
await setTimeout(() => console.log(1), 3000)
console.log(2)
console.log(3)
}
noResolveOrReject()
console.log(4)
// 输出:4,2,3,1
</script>
\