异步 允许一个任务启动后,程序继续执行其他任务,而无需等待这个任务完成,它与同步是相对的概念

异步编程的特点:

  • 任务可以并发执行,程序不会因为等待某个任务而被阻塞,这样可以避免页面冻结或卡顿
  • 当异步任务完成时,可以通过回调函数、Promise 或 async/await 的方式来处理结果
  • 异步编程更适合处理 I/O 操作,如 网络请求、文件或数据库操作等耗时任务…

Promise 对象

Promise 是一种用于处理异步操作的对象,它表示一个尚未完成但将在将来可能会完成的操作,它可以在异步操作完成后传递结果(成功或失败),从而让开发者能够避免“回调地狱”并使异步代码更易于管理和阅读

回调函数( callback ):

回调函数是一个被作为另一个函数参数传递的函数,它是异步编程的一种方式,在异步操作完成时会调用这个回调函数

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 例1 */
function fetchData(callback) {
// 异步任务
setTimeout(() => {
const data = '数据';
callback(data);
}, 1000);
}

fetchData((result) => {
console.log(result); // 输出 '数据'
});

/* 例2 */
document.addEventListener('keydown', (event) => {
console.log('keydown')
})

回调地狱( callback hell ):

回调地狱是指在异步编程中,嵌套过多的回调函数导致代码结构复杂、难以阅读和维护的情况。它通常出现在 js 中使用回调函数处理异步操作时,由于需要嵌套多个异步操作,每个操作的结果可能依赖于上一个操作,代码逐渐向右缩进,形成像“金字塔”一样的结构

在 Web 开发中最常见的异步操作就是发起网络请求。当你发起网络请求时,你可以传递一个回调函数,当请求响应时,这个回调函数会被调用

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 假设我们有几个异步网络请求操作需要按顺序执行,比如:获取用户数据、获取用户的订单、获取订单的详细信息。使用纯回调的写法大致如下
getUser(userId, (user) => {
console.log('用户数据:', user);

getOrders(user.id, (orders) => {
console.log('用户订单:', orders);

getOrderDetails(orders[0].id, (orderDetails) => {
console.log('订单详情:', orderDetails);

// 继续嵌套其他异步操作...
});
});
});

// 假设 getUser, getOrders, getOrderDetails 都返回 Promise
// 使用 Promise 改写大致如下
getUser(userId)
.then(user => {
console.log('用户数据:', user);
return getOrders(user.id); // 返回一个 Promise
})
.then(orders => {
console.log('用户订单:', orders);
return getOrderDetails(orders[0].id); // 返回一个 Promise
})
.then(orderDetails => {
console.log('订单详情:', orderDetails);
// 如果有其他异步操作,可以继续链式调用
})
.catch(error => {
console.error('发生错误:', error); // 集中处理错误
});

Promise 基本概念

Promise 有三种状态:

  • Pending(待定):初始状态,表示异步操作既没有完成也没有失败

  • Fulfilled(已完成):表示异步操作完成,并返回了一个结果( value / result )

  • Rejected(已拒绝):表示异步操作失败,并返回了一个原因或错误( reson / error )

Promise 的状态一旦变为 fulfilledrejected,就不会再改变

创建 Promise

语法:

1
2
3
4
const promise = new Promise(executor)
/*
executor 执行器函数,它是创建 Promise 类型对象时执行的函数,用于封装异步操作
*/

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建一个 Promise 类型对象
const promise = new Promise((resolve, reject) => {
// 模拟一个异步操作(假设)
const success = true;

if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
});

promise.then(
value => console.log(value), // promise 状态为 Fulfilled 时执行,value 为操作成功的结果
error => console.log(error) // promise 状态为 Rejected 时执行,error 为操作失败的结果
) // 操作成功

promises.png

resolve()

resolve 函数代表异步操作成功时会调用的函数,我们可以将异步操作成功的结果,作为参数传递出去

resolve 函数在调用时,会将 promise 的状态从 Pending 修改为 Fulfilled,此时 promise 的状态和结果就会固定,其它任何操作都无法再次修改 promise 的状态

reject()

reject 函数代表异步操作失败时( 比如 抛出错误 )会调用的函数,我们可以将异步操作错误的结果,作为参数传递出去

resolve 函数在调用时,会将 promise 的状态从 Pending 修改为 Rejected,此时 promise 的状态和结果就会固定,其它任何操作都无法再次修改 promise 的状态

promise.then()

then 方法的参数中包含两个函数,第一个是 promise 状态为 Fulfilled 时执行的函数,第二个是 promise 状态为 Rejected 时执行的函数,它们都可以接受参数来获取成功和失败的结果

Promise 静态方法

Promise.resolve(value)

用于直接返回一个状态为 Fulfilled 的 Promise 类型对象

Promise.reject(error)

用于直接返回一个状态为 Rejected 的 Promise 类型对象

Promise.all()

Promise.all() 用于处理多个 Promise 类型对象。Promise.all() 可以接收多个 Promise 类型对象作为输入,当所有输入的 Promise 类型对象的状态都为 Fulfilled 时,则返回一个状态为 Fulfilled 并且包含多个结果的 Promise 类型对象

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3]) // 全部成功,状态 Fulfilled
.then(values => {
console.log(values); // [1, 2, 3]
}, error => {
console.log(error);
});

const promise4 = Promise.reject(4);

Promise.all([promise1, promise2, promise3, promise4]) // 有一个失败,状态 Rejected
.then(values => {
console.log(values);
}, error => {
console.log(error); // 4
});

Promise.race()

Promise.race() 用于处理多个 Promise 类型对象,并返回第一个完成( 成功或失败 )的 Promise 类型对象

即那个异步操作最先完成( 成功或失败 )就处理那个,其它没有完成的就不等了

Promise 链式调用

Promise 链式调用是指多个 Promise 类型对象通过 .then() 方法串联在一起的操作方式,从而避免了回调地狱的问题

每个 .then() 方法都会返回一个新的 Promise 类型对象,从而可以继续链式调用

你也可以手动返回一个 Promise 类型对象,如果手动返回的是一个非 Promise 类型对象,则也会将其包装为 Promise 类型对象返回

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
new Promise((resolve, reject) => {
// 模拟一个异步操作(假设)
const success = true;

if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
})
.then(value => {
console.log(value); // 操作成功
// 这里默认返回 undefined, 会将其包装为 Promise 类型对象返回
})
.then(value => {
console.log(value) // undefined
return 'OK' // 包装为 Promise 类型对象返回
})
.then(value => {
console.log(value) // OK
// 显示的返回 Promise 类型对象
return Promise.reject('Fail')
})
.catch(error => {
// 集中处理错误
console.log(error) // 'Fail'
})

用 js 手写 Promise

  • 错误处理:Promise 提供了链式的 .catch() 方法,可以集中处理错误,避免在每个回调中重复处理错误