1.异步编程的实现方法
回调函数
promise
generator
async
2.Promise
2.1 介绍Promise
总体上讲:Promise是异步操作的结果
(从then角度来介绍)
Promise可以用来解决多层回调嵌套,用链式表达回调,容易理解和阅读
Promise可以用于解决回调函数中的错误,多层回调嵌套中的错误难以传播到发起者,用Promise中的catch容易解决
Promise表示一次异步计算的结果,不能用于多次异步计算
2.2 根据PromiseA+规范实现Promise
class VPromise {
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
static REJECT_ERROR = "can't reject yourself";
static RESOLVE_ERROR = "can't resolve yourself";
static STATUS_ERROR = "wrong status change";
#status = VPromise.PENDING;
#value;
#reason;
#fulfilledCallbacks = [];
#rejectedCallbacks = [];
constructor(func) {
try {
typeof func === "function" &&
func.call(this, this.#resolve.bind(this), this.#reject.bind(this));
} catch (err) {
this.#reject(err);
}
}
#resolve(value) {
if (this.#status !== VPromise.PENDING) return;
if (value === this) throw new TypeError(VPromise.RESOLVE_ERROR);
if (value instanceof VPromise) {
value.then(
(res) => this.#resolve(res),
(err) => this.#reject(err)
);
} else {
this.#value = value;
this.#status = VPromise.FULFILLED;
(Array.isArray(this.#fulfilledCallbacks)
? this.#fulfilledCallbacks
: []
).forEach((func) => {
typeof func === "function" && func(this.#value);
});
}
}
#reject(reason) {
if (this.#status !== VPromise.PENDING) return;
if (reason === this) throw new TypeError(VPromise.REJECT_ERROR);
if (reason instanceof VPromise) {
reason.then(
(res) => this.#reject(res),
(err) => this.#reject(err)
);
} else {
this.#reason = reason;
this.#status = VPromise.REJECTED;
(Array.isArray(this.#rejectedCallbacks)
? this.#rejectedCallbacks
: []
).forEach((func) => {
typeof func === "function" && func(this.#reason);
});
}
}
then(onFulfilledCallback, onRejectedCallback) {
const forMatFulfilledCallback =
typeof onFulfilledCallback === "function"
? onFulfilledCallback
: (value) => value;
const forMatRejectedCallback =
typeof onRejectedCallback === "function"
? onRejectedCallback
: (reason) => reason;
return new VPromise((resolve, reject) => {
if (this.#status === VPromise.PENDING) {
const fulfilledCallbacks = Array.isArray(this.#fulfilledCallbacks)
? this.#fulfilledCallbacks
: [];
fulfilledCallbacks.push((value) => {
try {
resolve(forMatFulfilledCallback(value));
} catch (err) {
reject(err);
}
});
this.#fulfilledCallbacks = fulfilledCallbacks;
const rejectedCallbacks = Array.isArray(this.#rejectedCallbacks)
? this.#rejectedCallbacks
: [];
rejectedCallbacks.push((reason) => {
try {
reject(forMatRejectedCallback(reason));
} catch (err) {
reject(err);
}
});
this.#rejectedCallbacks = rejectedCallbacks;
} else if (this.#status === VPromise.FULFILLED) {
queueMicrotask(() => {
try {
resolve(forMatFulfilledCallback(this.#value));
} catch (err) {
reject(err);
}
});
} else if (this.#status === VPromise.REJECTED) {
queueMicrotask(() => {
try {
reject(forMatRejectedCallback(this.#reason));
} catch (err) {
reject(err);
}
});
} else {
throw new Error(VPromise.STATUS_ERROR);
}
});
}
catch(onCatchCallback) {
return this.then(
null,
(reason) =>
typeof onCatchCallback === "function" && onCatchCallback(reason)
);
}
finally(onFianllyCallback) {
return this.then(
(value) => {
typeof onFianllyCallback === "function" && onFianllyCallback(value);
return value;
},
(reason) => {
typeof onFianllyCallback === "function" && onFianllyCallback(reason);
throw new Error(reason);
}
);
}
}
上述代码实现的PromiseA+部分说明:
-
Promise对象的状态分为Pending Fulfilled Rejected
-
then方法
(1) then方法接受两个参数onFulfilled和onRejected
(2) 这两个参数必须是函数,如果不是函数必须忽略。可以赋值成e => e。这样保证了在缺少回调函数时Promise链可以继续传递,catch和finally方法会利用这一点。
(3) then方法调用时立即返回一个Promise对象
(4) onFulfilled和onRejected只能在当前执行栈为空后才能执行
(5) onFulfilled和onRejected执行前需要判断状态,是Pending态需要加入回调函数队列,非Pending态立即兑现或拒绝 -
resolvePromise解决程序
(1)需要两个参数第一个参数promise是promise对象,第二个参数x是这个对象的解决值
(2)这两个参数不能是同一个引用
(3)当第二个参数是promise对象时,需要判断其状态,如果是Pending态,那么继续调用它的resolvePromise。否则以它的兑现值或拒绝值兑现。
(4)当第二个参数不是promise对象时,直接以它作为兑现值兑现。 -
catch方法
(1) catch方法接受一个错误处理回调函数
(2) catch方法为了维护promise链,也必须返回一个promise对象
(3) catch方法通过“return this.then(null, onRejected)”来实现,如果没有错误被捕获,那么由于null的存在,兑现处理函数将被赋值为“e => e”,这样就使得promise链上一层的兑现值继续向下传递,绕过catch -
finally方法
(1) finally方法接受一个回调函数,这个回调函数没有参数,不会获取promise链上一层的兑现值或者拒绝值
(2) finally方法为了维护promise链,也必须返回一个promise对象
(3) finally方法的实现可以分为三步。首先返回上一层的then函数,来获取上一层的兑现值或者拒绝值。其次返回一个立即兑现的promise对象,在其中执行finally回调函数,最后给该promise对象注册回调,用于把上一层的兑现值或拒绝值传递给下一层。 -
错误处理和类型检查
(1) 构造函数中要检查参数是否是函数。用户传入的函数是不可靠的,需要try和catch
(2) then中用户传入的回调函数也是不可靠的,需要try和catch检查
2.3 实现并行和串行Promise
// 并行Promise的all方法
Promise.all = function(ar) {
if(ar && typeof ar[Symbol.iterator] !== "function")
throw new TypeError("all方法的参数必须是可迭代对象")
return new Promise((resolve, reject) => {
let cnt = 0
let ps = []
// 下面使用数组的快捷api进行遍历,所以这里将可迭代对象转化为数组
ar = [...ar]
// 如果是一个空的可迭代对象,那么立即返回一个以空数组兑现的Promise对象
if(ar.length === 0)
resolve([])
ar.forEach((p, index) => {
if(p instanceof Promise) {
p.then(res => {
ps[index] = res
if(++ cnt === ar.length)
resolve(ps)
}, err => {
reject(err)
})
} else {
// 考虑数组中非Promise的元素
ps[index] = p
if(++ cnt === ar.length)
resolve(ps)
}
})
})
}
// 并行Promise的allSettled方法
Promise.allSettled = function(ar) {
if(ar && typeof ar[Symbol.iterator] !== "function")
throw new TypeError("allSettled参数必须是可迭代对象")
return new Promise(resolve => {
let cnt = 0
let ps = []
// 将可迭代对象ar转化为数组
ar = [...ar]
// 如果是一个空的可迭代对象,那么立即返回一个以空数组兑现的Promise对象
if(ar.length === 0)
resolve([])
ar.forEach((el, index) => {
if(el instanceof Promise) {
el.then(res => {
ps[index] = { status: "fulfilled", value: res }
if(++ cnt === ar.length)
resolve(ps)
}, err => {
ps[index] = { status: "rejected", reason: err }
if(++ cnt === ar.length)
resolve(ps)
})
} else {
ps[index] = { status: "fulfilled", value: el }
if(++ cnt === ar.length)
resolve(ps)
}
})
})
}
Promise.any = function(ar) {
if(ar && typeof ar[Symbol.iterator] !== "function")
throw new TypeError("any方法的参数必须是可迭代对象")
return new Promise((resolve, reject) => {
ar = [...ar]
// Promise规范说明any方法中需要考虑length
if(ar.length === 0) {
reject("All Promises are rejected")
}
// 不同于all和race方法,any方法返回第一个兑现的Promise对象
ar.forEach(p => {
if(p instanceof Promise)
p.then(resolve)
else
resolve(p)
})
// 如果所有Promise都被拒绝,那么该返回期约也会被拒绝。由于Promise状态机状态不可逆转,因此不用设置计数器判断期约是否全部被拒绝
reject("All Promises are rejected")
})
}
// 并行Promise的race方法
Promise.race = function(ar) {
if(ar && typeof ar[Symbol.iterator] !== "function")
throw new TypeError("race方法的参数必须是可迭代对象")
return new Promise((resolve, reject) => {
// 将可迭代对象转化为数组
ar = [...ar]
// 在Promise规范中没有说明race需要考虑ar是空迭代对象的情况
ar.forEach(el => {
if(el instanceof Promise) {
el.then(resolve, reject)
} else
resolve(el)
})
})
}
// 串行Promise的iterate方法
Promise.iterate = function(ar) {
if(ar && typeof ar[Symbol.iterator] !== "function")
throw new TypeError("iterate方法的参数必须是可迭代对象")
let promise = new Promise(res => res())
ar = [...ar]
ar.forEach(el => {
promise = promise.then(() => el)
})
return promise
}
讲一讲Promise.all和Promise.allSettled和Promise.any和Promise.race和串行Promise:
-
Promise.all作用是并行执行传入的数组(可迭代对象)中的期约。Promise.all立即返回一个Promise对象,这个Promise对象只有在数组中的期约全部兑现后才兑现,兑现值为一个数组保存它们的兑现值。或者在其中某一个期约被拒绝后立即拒绝,拒绝值为数组中拒绝期约的拒绝值。
-
Promise.allSettled和Promise.all类似,只不过是不拒绝期约,等到所有期约兑现或拒绝后才兑现。返回值是一个数组,数组每一项是一个对象,status用来描述状态,value/reason用来描述兑现值/拒绝值。
-
Promise.any和Promise.all和Promise.race类似。对于传入的可迭代对象,Promise.any会返回第一个兑现的Promise的兑现值为兑现值的Promise。如果所有Promise都被拒绝,那么该方法会返回一个被拒绝的Promise对象,拒绝原因是“all promises are rejected”
-
Promise.race作用是并行执行传入的数组中的期约。Promise.race立即返回一个Promise对象。
这个对象只有在数组中最快兑现的期约兑现时才兑现,兑现值为这个最快兑现值。或者这个对象只有在数组中最快拒绝的期约拒绝时才拒绝,拒绝值是这个最快拒绝值。 -
Promise.iterate作用是顺序执行某些期约。实现是创建一个立即解决的Promise,然后调用它的then方法,第一个期约作为then方法的回到函数。然后更新这个Promise为Promise.then(),这个效果看起来就像毛毛虫。可以把最初创建的Promise看做毛毛虫的头,更新时相当于毛毛虫的身体多了一节,以此类推毛毛虫不断变长。
2.4 Promise的中断
解释: Promise中的函数是不可被中断的,除非有try/catch捕捉到的错误。实际上我们想达到的效果是不让Promise链正常执行。
思路: Promise的中断用到2.3中的串行期约。思路是想要让一个Promise变成可中断的,就创建一个辅助Promise,并利用闭包机制暴露其reject拒绝函数。最后将这两个Promise放入Promise.race中竞速。我们获取reject函数后就可以调用reject让race竞速中断,获取辅助Promise,制造了Promise被中断的假象。
方案一:客户端和服务端通用
// 生成一个可被中断的期约
function abortPromise(promise) {
let abortFunc = null
let abort = new Promise((res, rej) => abortFunc = rej)
return {
promise: Promise.race([promise, abort]),
abort: abortFunc
}
}
// 模拟随机生成一个3秒之内完成的网络请求
function netRequestMaker() {
return new Promise(res => {
setTimeout(() => {
res("成功")
}, Math.random() * 3000)
})
}
// 测试函数
(function test() {
// 创建十个样例测试
for(let i = 0; i < 10; i ++) {
let { promise, abort } = abortPromise(netRequestMaker())
promise.then(() => {
console.log("网络请求发送成功")
}, () => {
console.log("网络请求发送超时")
})
// 假设2秒还没有完成的网络请求为超时请求,需要终止
setTimeout(() => {
abort()
}, 2000)
}
})();
方案二:客户端的AbortController
<script>
// 生成一个可被中断的期约
function abortPromise(promise) {
let controller = new AbortController()
let signal = controller.signal
return {
promise: Promise.race([promise, new Promise((res, rej) => signal.addEventListener("abort", rej))]),
controller: controller
}
}
// 中断时使用controller.abort()即可
</script>
3.生成器和迭代器
注意:复习一下迭代器协议。可迭代对象拥有迭代器方法,迭代器方法产生迭代器对象,迭代器对象调用next()产生迭代器结果对象。
- 可迭代对象
- 迭代器方法
- 迭代器对象
- 迭代器结果对象
3.1 迭代器
用迭代器实现map功能
function iteratorMaker(ar, assert) {
let i = 0
let length = ar.length
return {
[Symbol.iterator]() {
return this;
},
next() {
while(assert(ar[i]) === false && i < length - 1)
i ++
if(i === length)
return { value: undefined, done: true }
else
return { value: ar[i ++], done: false}
}
}
}
let ar = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let iterator = iteratorMaker(ar, el => el % 2 === 0)
for(let i = iterator.next(); i.done !== true; i = iterator.next())
console.log(i.value)
3.2 生成器
// yield的值
function* generatorMaker() {
let ans1 = yield 1
console.log(ans1)
let ans2 = yield 2
console.log(ans2)
return "完成"
}
let gen = generatorMaker();
console.log(gen.next().value)
console.log(gen.next("Vanghua").value)
console.log(gen.next("Danny").value)
// 1 Vanghua 2 Danny 完成
// 在类中替换迭代器
class Tester {
constructor() {
}
*[Symbol.iterator]() {
// 顺序的yield可以解决回调地狱
yield 1
yield 2
}
}
let test = new Tester()[Symbol.iterator]()
console.log(...test) // 1 2
4.async和await
4.1 介绍async和await
async和await作用是把JavaScript异步编程写得像同步编程
4.2 使用async和await
1.async函数返回一个期约
2.await只能在async函数中使用
3.await后跟上期约表示获取这个期约的兑现值或拒绝值
4.await相当于同步操作,await语句后面的语句会被await阻塞。实际上await后面的语句相当于期约的then,会被加入到微任务队列。
4.3 实现async和await
实际上是利用生成器和生成器自动执行来实现。可以把生成器函数function *
看做async function。可以把yield看做await
// 把function *看做是async,把yield看做是awaits
function *generator() {
yield new Promise(res => res("p1"))
yield 1
yield new Promise((res, rej) => rej("p2"))
yield "Danny"
}
(function generatorAuto(generator) {
let iter = generator.next();
(function next() {
// 如果await后面的值是promise对象
if(iter.value instanceof Promise)
iter.value.then(res => {
console.log(res)
iter = generator.next()
next()
}, err => {
console.log(err)
iter = generator.next()
next()
})
// 如果await后面的值不是promise对象
else if(!iter.done) {
console.log(iter.value)
iter = generator.next()
next()
}
})()
})(generator())
4.4 for/await
在循环中如果可迭代对象的每一项都是期约,那么可以考虑使用for/await,会自动将每一项做await处理。
这样还有一个好处,因为await是同步的,这相当于实现了前面提到的串行promise,一个for/await就实现了。
for/await是用来遍历异步迭代器的。
;(async function () {
let ar = []
for(let i = 0; i < 10; i ++)
ar.push(fetch("/test").then(res => res.text()))
for await(let promise of ar) {
console.log(promise)
}
})();
4.5 异步迭代器
其实这个概念上面已经接触到了类似的,在考虑async/await原理时使用的生成器+自动执行函数,
每次yield的都是一个Promise,这相当于生成器结果对象的value是一个promise。
而异步迭代器是指迭代器结果对象整体就是一个promise对象,{value, done}是该promise对象的解决值
;(async function () {
// 将传入的可迭代对象包装,生成一个异步迭代器
function asyncMaker(ar) {
let i = 0
return {
[Symbol.asyncIterator]() {
return this
},
next() {
return new Promise((resolve, reject) => {
if(i === ar.length)
resolve({value: undefined, done: true})
if(ar[i] instanceof Promise)
ar[i ++].then(res => resolve({value: res, done: false}))
else
resolve({value: ar[i ++], done: false})
})
}
}
}
let ar = []
for(let i = 0; i < 10; i ++) {
ar.push(fetch("/test").then(res => res.text()))
}
let asyncAr = asyncMaker(ar)
// for await来遍历异步迭代器。看来这是实现串行期约的好方法
for await(let promise of asyncAr)
console.log(promise)
})();
4.5.1 异步迭代器的迭代
下面列举了三种异步迭代器的迭代方法(for await略去)
- 普通循环迭代:循环任务会一直占用主线程,微任务无法执行,这种迭代是无效的
- 递归迭代:这种递归是线性递归,可以有效迭代异步迭代器
- async和await循环迭代:比递归迭代更节省空间,循环产生的块级作用域占用的内存比递归产生的函数作用域占用的内存小的多。await阻塞性使得微任务可以正常被执行。
// 异步迭代器生成函数
function asyncMaker(ar) {
let i = 0
return {
[Symbol.asyncIterator]() {
return this
},
next() {
return new Promise((resolve, reject) => {
if(i === ar.length)
resolve({value: undefined, done: true})
if(ar[i] instanceof Promise)
ar[i ++].then(res => resolve({value: res, done: false}))
else
resolve({value: ar[i ++], done: false})
})
}
}
}
// 数据生成器,这里生成1000以内的斐波那契数
function dataMaker() {
let a = 1, b = 1, ar = []
while(a < 1000) {
ar.push(new Promise(res => res(a)));
[a, b] = [b, a + b];
}
return ar
}
// 普通的循环迭代异步迭代器
// 一直无法等到微任务执行
function loopIterate(asyncIter) {
let breakFlag = false, iter = 0
while(true) {
// 会一直输出,因为主任务一直在执行,执行找一直有任务在执行,任务时while循环
console.log(++ iter)
asyncIter.next().then(({done, value}) => {
if(done)
breakFlag = true
else
console.log(value)
})
}
}
// 递归迭代异步迭代器
function funcIterate(asyncIter) {
asyncIter.next().then(({done, value}) => {
if(!done) {
console.log(value)
funcIterate(asyncIter)
}
})
}
// async和await的循环迭代异步迭代器
async function asyncLoopIterate(asyncIter) {
while(true) {
// 阻塞式进行,while循环执行到await停止,等待promise的结果
let { done, value } = await asyncIter.next()
if(done)
break
else
console.log(value)
}
}
5.完整版Promise实现
class VPromise {
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
static REJECT_ERROR = "can't reject yourself";
static RESOLVE_ERROR = "can't resolve yourself";
static STATUS_ERROR = "wrong status change";
#status = VPromise.PENDING;
#value;
#reason;
#fulfilledCallbacks = [];
#rejectedCallbacks = [];
constructor(func) {
try {
typeof func === "function" &&
func.call(this, this.#resolve.bind(this), this.#reject.bind(this));
} catch (err) {
this.#reject(err);
}
}
#resolve(value) {
if (this.#status !== VPromise.PENDING) return;
if (value === this) throw new TypeError(VPromise.RESOLVE_ERROR);
if (value instanceof VPromise) {
value.then(
(res) => this.#resolve(res),
(err) => this.#reject(err)
);
} else {
this.#value = value;
this.#status = VPromise.FULFILLED;
(Array.isArray(this.#fulfilledCallbacks)
? this.#fulfilledCallbacks
: []
).forEach((func) => {
typeof func === "function" && func(this.#value);
});
}
}
#reject(reason) {
if (this.#status !== VPromise.PENDING) return;
if (reason === this) throw new TypeError(VPromise.REJECT_ERROR);
if (reason instanceof VPromise) {
reason.then(
(res) => this.#reject(res),
(err) => this.#reject(err)
);
} else {
this.#reason = reason;
this.#status = VPromise.REJECTED;
(Array.isArray(this.#rejectedCallbacks)
? this.#rejectedCallbacks
: []
).forEach((func) => {
typeof func === "function" && func(this.#reason);
});
}
}
then(onFulfilledCallback, onRejectedCallback) {
const forMatFulfilledCallback =
typeof onFulfilledCallback === "function"
? onFulfilledCallback
: (value) => value;
const forMatRejectedCallback =
typeof onRejectedCallback === "function"
? onRejectedCallback
: (reason) => reason;
return new VPromise((resolve, reject) => {
if (this.#status === VPromise.PENDING) {
const fulfilledCallbacks = Array.isArray(this.#fulfilledCallbacks)
? this.#fulfilledCallbacks
: [];
fulfilledCallbacks.push((value) => {
try {
resolve(forMatFulfilledCallback(value));
} catch (err) {
reject(err);
}
});
this.#fulfilledCallbacks = fulfilledCallbacks;
const rejectedCallbacks = Array.isArray(this.#rejectedCallbacks)
? this.#rejectedCallbacks
: [];
rejectedCallbacks.push((reason) => {
try {
reject(forMatRejectedCallback(reason));
} catch (err) {
reject(err);
}
});
this.#rejectedCallbacks = rejectedCallbacks;
} else if (this.#status === VPromise.FULFILLED) {
queueMicrotask(() => {
try {
resolve(forMatFulfilledCallback(this.#value));
} catch (err) {
reject(err);
}
});
} else if (this.#status === VPromise.REJECTED) {
queueMicrotask(() => {
try {
reject(forMatRejectedCallback(this.#reason));
} catch (err) {
reject(err);
}
});
} else {
throw new Error(VPromise.STATUS_ERROR);
}
});
}
catch(onCatchCallback) {
return this.then(
null,
(reason) =>
typeof onCatchCallback === "function" && onCatchCallback(reason)
);
}
finally(onFianllyCallback) {
return this.then(
(value) => {
typeof onFianllyCallback === "function" && onFianllyCallback(value);
return value;
},
(reason) => {
typeof onFianllyCallback === "function" && onFianllyCallback(reason);
throw new Error(reason);
}
);
}
static all(promiseList) {
return new VPromise((resolve, reject) => {
const result = [];
(Array.isArray(promiseList) ? promiseList : []).forEach(
(promise, index) => {
if (promise instanceof VPromise) {
promise.then((value) => {
result[index] = value;
result.length === promiseList.length && resolve(result);
}, reject);
} else {
result[index] = promise;
result.length === promiseList.length && resolve(result);
}
}
);
});
}
static allSettled(promiseList) {
return new VPromise((resolve) => {
const result = [];
(Array.isArray(promiseList) ? promiseList : []).forEach(
(promise, index) => {
if (promise instanceof VPromise) {
promise
.then(
(value) => {
result[index] = {
status: VPromise.FULFILLED,
value,
};
},
(reason) => {
result[index] = {
status: VPromise.REJECTED,
reason,
};
}
)
.then(
() => result.length === promiseList.length && resolve(result)
);
} else {
result[index] = {
status: VPromise.FULFILLED,
value: promise,
};
result.length === promiseList.length && resolve(result);
}
}
);
});
}
static race(promiseList) {
return new VPromise((resolve, reject) => {
(Array.isArray(promiseList) ? promiseList : []).forEach(
(promise) =>
promise instanceof VPromise && promise.then(resolve, reject)
);
});
}
static any(promiseList) {
return new VPromise((resolve, reject) => {
const result = [];
(Array.isArray(promiseList) ? promiseList : []).forEach(
(promise, index) => {
if (promise instanceof VPromise) {
promise.then(resolve, (reason) => {
result[index] = reason;
result.length === promiseList.length && reject(result);
});
}
}
);
});
}
static withResolvers() {
let resolve;
let reject;
const promise = new VPromise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
}