"红宝书" 通常指的是《JavaScript 高级程序设计》,这是一本由 Nicholas C. Zakas(尼古拉斯·扎卡斯)编写的 JavaScript 书籍,是一本广受欢迎的经典之作。这本书是一部翔实的工具书,满满的都是 JavaScript 知识和实用技术。
不管你有没有刷过红宝书,如果现在还没掌握好,那就一起来刷红宝书吧,go!go!go!
系列文章:
第一部分:基本知识(重点、反复阅读)
第二部分:进阶内容(重点、反复阅读)
- 前端必刷系列之红宝书------第 7 章
- 前端必刷系列之红宝书------第 8 章
- 前端必刷系列之红宝书------第 9 章
- 前端必刷系列之红宝书------第 10 章
- 前端必刷系列之红宝书------第 11 章
第 11 章 期约与异步函数
ES6 新增期约。ES8 新增异步函数。ES 的异步编程特性有了长足的进步。
通过期约 Promise 和异步函数 async/await,不仅可以实现之前难以实现或不可能实现的任务,而且也能写出更清晰、简洁,并且容易理解、调试的代码。
期约
在 JavaScript 中,期约(Promise) 是一种用于处理异步操作的对象。期约提供了一种更结构化的方式来处理异步代码,避免了回调地狱(Callback Hell)的问题。
期约的主要功能是为异步代码提供了清晰的抽象。可以用期约表示异步执行的代码块
,也可以用期约表示异步计算的值
。
在需要串行异步代码时
,期约的价值最为突出。
作为可塑性极强的一种结构,期约可以被序列化、连锁使用、复合、扩展和重组
。
期约基础
- 期约是一种用于处理异步操作的对象,具有三个状态:
pending
(等待中)、fulfilled
(已成功)和rejected
(已拒绝)。 - 通过
new Promise(executor)
构造函数创建期约,executor
函数包含异步操作的代码,并接受resolve
和reject
两个参数。 - 使用
.then()
处理成功状态,.catch()
处理拒绝状态,.finally()
处理期约结束时的逻辑。
期约链式调用
.then()
返回一个新的期约,可以通过链式调用.then()
和.catch()
形成期约链。- 在链式调用中,每个
.then()
都可以返回一个值或期约,传递给下一个.then()
。
.all() 和 .race() 和 .allSettled()
Promise.all(iterable)
用于处理多个期约,只有所有期约都成功才算成功,任何一个期约失败都将导致整体失败。Promise.race(iterable)
在第一个期约完成时返回其结果,无论成功还是拒绝。Promise.allSettled(iterable)
:静态方法,返回一个期约,等到所有期约都已解决或拒绝后才解决,结果是一个数组,每个元素都包含一个对象,表示对应的期约是解决还是拒绝。
.resolve() 和 .reject()
Promise.resolve(value)
创建一个已解决的期约,将value
作为期约的结果。Promise.reject(reason)
创建一个已拒绝的期约,将reason
作为期约的拒因。
错误处理
- 使用
.catch()
或try/catch
语句捕获期约的拒绝。 - 可以通过在
.then()
链中使用第二个参数处理错误。
手写 Promise
js
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function myPromise(fn) {
const self = this
this.state = PENDING
this.value = null
this.resolvedCallbacks = []
this.rejectedCallbacks = []
function resolve(value) {
if(value instanceof myPromise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (self.state === PENDING) {
self.state = RESOLVED
self.value = value
self.resolvedCallbacks.forEach((callback) => {
callback(value)
})
}
}, 0)
}
function reject(value){
setTimeout(()=> {
if (self.state === PENDING) {
self.state = REJECTED
self.value = value
self.rejectedCallbacks.forEach((callback) => {
callback(value)
})
}
}, 0)
}
try{
fn(resolve, reject)
}catch(err){
reject(err)
}
}
myPromise.prototype.then = (onResolve, onReject) => {
onResolve = typeof onResolve === 'function' ? onResolve : function(value) { return value }
onReject = typeof onReject === 'function' ? onReject : function(error) { throw new Error('错误') }
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolve)
this.rejectedCallbacks.push(onReject)
}
if (this.state === RESOLVED) {
onResolve(this.value)
}
if(this.state === REJECTED) {
onReject(this.value)
}
}
手写Promise.then
js
function then(onResolve, onReject) {
const self = this
return new Promise((resolve, reject) => {
let fulfilled = () => {
try{
const res = onResolve(self.value)
return res instanceof myPromise ? res.then(resolve, reject) : resolve(res)
}catch(err){
reject(err)
}
}
let rejected = () => {
try{
const res = onReject(self.reason)
return res instanceof myPromise ? res.then(resolve, reject) : reject(res)
}catch(err){
reject(err)
}
}
switch(self.status){
case PENDING:
self.resolvedCallbacks.push(fulfilled)
self.rejectedCallbacks.push(rejected)
break;
case RESOLVED:
fulfilled()
break;
case REJECT:
rejected()
break;
}
})
}
手写Promise.all
js
function promiseAll(promises){
if(!Array.isArray(promises)){
throw new Error('must be an array')
}
return new Promise((resolve, reject) => {
const count = promises.length
let num = 0
const res = []
for(let i=0; i<count; i++){
Promise.resolve(promises[i]).then((value)=>{
num++
res[i] = value
if(res.length === count){
resolve(res)
}
}).catch(error => {
reject(error);
});
}
})
}
手写Promise.race
js
function promiseRace(promises){
if(!Array.isArray(promises)){
throw new Error('must be an array')
}
return new Promise((resolve, reject)=>{
for(let i=0; i<promises.length; i++){
promises[i].then(value => {
resolve(value);
})
.catch(error => {
reject(error);
});
}
})
}
手写Promise.any
js
function promiseAny(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject(new TypeError('promises must be an array'));
}
const errors = [];
promises.forEach(promise => {
Promise.resolve(promise)
.then(result => {
resolve(result);
})
.catch(error => {
errors.push(error);
if (errors.length === promises.length) {
reject(new AggregateError('All promises were rejected', errors));
}
});
});
});
}
手写Promise.allSettled
js
function promiseAllSettled(promises) {
return new Promise(resolve => {
if (!Array.isArray(promises)) {
reject(new TypeError('promises must be an array'));
}
const results = [];
let completedPromises = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(result => {
results[index] = { status: 'fulfilled', value: result };
})
.catch(reason => {
results[index] = { status: 'rejected', reason: reason };
})
.finally(() => {
completedPromises++;
if (completedPromises === promises.length) {
resolve(results);
}
});
});
});
}
手写Promise.finally
js
Promise.prototype.finally = (cb) => {
return this.then(
value => Promise.resolve(cb()).then(()=>value),
err => Promise.reject(cb()).then(()=> throw new Error(err))
)
}
异步函数
异步函数通常是指使用async/await
语法糖来处理异步操作的函数。
异步函数可暂停执行,而不阻塞主线程
。
async 函数
async
关键字用于定义一个异步函数。异步函数返回一个期约(Promise)。- 异步函数内部可以包含
await
表达式,它会暂停函数执行,等待期约解决,并返回期约的结果。
await 表达式
await
表达式只能在异步函数内部使用,用于等待期约的解决。await
后面可以是一个期约对象,也可以是返回期约的函数调用或任何返回值为期约的表达式。
错误处理
- 使用
try/catch
语句来捕获异步函数内的错误。 - 如果期约被拒绝,将会抛出一个异常,可以通过
catch
捕获。
js
async function example() {
try {
const result = await someAsyncFunction();
console.log(result);
} catch (error) {
console.error(error);
}
}
多个异步操作的串行执行
- 使用
await
可以实现多个异步操作的串行执行,确保一个操作完成后再执行下一个。
js
async function serialAsyncOperations() {
const result1 = await operation1();
const result2 = await operation2(result1);
const result3 = await operation3(result2);
return result3;
}
未完待续...
参考资料
《JavaScript 高级程序设计》(第 4 版)