一、 为什么需要异步编程?
先说说为什么要有异步这回事. JavaScript是单线程的, 也就是说它一次只能做一件事. 如果所有操作都同步执行, 遇到网络请求或者文件读取这种耗时的操作, 页面就会卡住不动, 用户体验直接爆炸.
// 同步代码的灾难现场constdata = fetchDataFromServer();// 假设这个请求要3秒console.log(data);// 这行代码要傻等3秒才能执行doSomethingElse();// 更要再等
所以JavaScript采用异步非阻塞的方式处理这类操作: 发起请求后不干等着, 而是继续执行后面的代码, 等请求完成后再来处理结果. 这就引出了我们最原始的处理方式——回调函数.
二、回调函数的时代
1) 基础回调:
// 最简单的回调示例functionfetchData(callback) {setTimeout(() => {callback('数据到手啦!');},1000);}
fetchData((data) => {console.log(data);// 1秒后输出"数据到手啦!"});2) 回调地狱: 当多个异步操作需要顺序执行时, 代码就会变成这样:// 经典回调地狱getUserInfo(userId,function(user) {getOrders(user.id,function(orders) {getOrderDetails(orders[0].id,function(details) {getProductInfo(details.productId,function(product) {console.log('最终结果:', product);});});});});这种代码的问题:
1. 难以阅读和维护(金字塔形状)
2. 错误处理非常麻烦(要在每个回调里处理错误)
3. 无法使用return和throw
4. 无法使用for循环和try/catch
3) 回调的改进尝试
2.1 命名函数, 把匿名回调变成命名函数:
getUserInfo(userId, handleUser);
functionhandleUser(user) {getOrders(user.id, handleOrders);}
functionhandleOrders(orders) {getOrderDetails(orders[0].id, handleDetails);}// ...虽然可读性好点了,但代码跳来跳去更晕了2.2 Async.js库, 提供series/parallel/waterfall等流程控制:async.waterfall([functiongetUser(callback) {getUserInfo(userId, callback);},functiongetOrders(user, callback) {getOrders(user.id, callback);}// ...],functionfinalHandler(err, result) {// 最终处理});这些方案虽然有一定效果, 但都不够优雅. 直到Promise的出现.
三、Promise
3.1 Promise直译就是"承诺", 它表示一个异步操作的最终完成或失败. Promise有三种状态:
1. pending(进行中)
2. fulfilled(已成功)
3. rejected(已失败)
一旦状态改变就不会再变(要么成功要么失败).
const promise =newPromise((resolve, reject) => {// 异步操作setTimeout(() => {if (Math.random() >0.5) {resolve('成功数据');}else {reject(newError('失败原因'));}},1000);});
promise.then(data=> {console.log('成功:', data);}).catch(err=> {console.error('失败:', err);});3.2 Promise链式调用, 这是Promise最强大的地方:// 用Promise改造回调地狱getUserInfo(userId).then(user=>getOrders(user.id)).then(orders=>getOrderDetails(orders[0].id)).then(details=>getProductInfo(details.productId)).then(product=> {console.log('最终结果:', product);}).catch(err=> {console.error('出错啦:', err);});对比之前的回调地狱, 这种链式调用:
1. 扁平化, 可读性极佳
2. 错误处理统一(一个catch处理所有错误)
3. 可以使用return传递数据
4. 可以在then里throw error
3.3 Promise的静态方法: Promise.all: 等所有Promise都成功.Promise.all([promise1, promise2, promise3]).then(([result1, result2, result3]) => {// 所有都成功才会到这里}).catch(err=> {// 任意一个失败就到这里});Promise.race:竞速, 第一个完成的Promise决定结果.Promise.race([fetchFromA(),fetchFromB()]).then(firstResult=> {// 使用最先返回的结果});Promise.allSettled:等所有Promise都完成(无论成功失败).Promise.allSettled([promise1, promise2]).then(results=> {results.forEach(result=> {if (result.status ==='fulfilled') {console.log('成功:', result.value);}else {console.log('失败:', result.reason);}});});3.4 Promise的局限性虽然Promise很棒, 但还是有不足:
1. 无法取消Promise.
2. 错误处理虽然比回调好, 但还是要用catch.
3. 多个Promise之间的数据共享不太方便.
4. 调试时堆栈信息不友好.
四、aysnc/await
async/await是现代JavaScript异步编程的终极解决方案, 它让异步代码看起来和同步代码一样直观, 用起来更简单:
asyncfunctionfetchData() {try {const user =awaitgetUserInfo(userId);const orders =awaitgetOrders(user.id);const details =awaitgetOrderDetails(orders[0].id);console.log(details);}catch (err) {console.error(err);}}
这代码看起来完全是同步的风格, 但实际上是异步执行的!4.1 async/await函数的特征总是返回Promise:1) 如果返回非Promise值, 会自动包装成Promise.resolve(值)
2) 如果抛出异常, 返回Promise.reject(异常)
await只能在async函数中使用:
1) 普通函数中使用会报语法错误
2) 现代浏览器和Node.js的模块系统中支持顶层await
4.2 await解析
4.2.1 await的行为
asyncfunctionexample() {const result =await somePromise;console.log(result);}这段代码实际上做了以下几件事:
1. 暂停example函数的执行
2. 等待somePromise完成
3. 如果Promise成功, 恢复执行并将结果赋值给result
4. 如果Promise失败, 抛出拒绝原因(可以用try/catch捕获)
4.2.2 await不一定要等Promise
虽然设计初衷是处理Promise, 但await可以等待任何值:
asyncfunctionfoo() {const a =await42;// 等价于 await Promise.resolve(42)const b =awaitsomeFunction();// 如果someFunction返回非Promise,也会自动包装}4.2.3 错误处理机制
await的错误处理非常符合直觉, 可以用传统的try/catch:
asyncfunctionfetchData() {try {const data =awaitfetch('/api');const json =await data.json();console.log(json);}catch (err) {console.error('请求失败:', err);}}对比Promise的.catch(), 这种写法更接近同步代码的思维模式.
五、常见使用方式
5.1 顺序执行:
asyncfunctionsequential() {const user =awaitgetUser();const posts =awaitgetPosts(user.id);const comments =awaitgetComments(posts[0].id);return comments;}5.2 并行执行:
asyncfunctionparallel() {const [user, posts] =awaitPromise.all([getUser(),getPosts()]);return { user, posts };}5.3 循环中的await:
// 顺序处理数组asyncfunctionprocessArray(array) {for (const itemof array) {awaitprocessItem(item);}}
// 并行处理数组asyncfunctionprocessArrayParallel(array) {awaitPromise.all(array.map(item=>processItem(item)));}5.4 await可以等待任何thenable对象:
asyncfunctionexample() {const result =await {then(resolve) {setTimeout(() =>resolve('done'),1000);}};console.log(result);// 1秒后输出'done'}5.5 混合使用Promise和async/await:
asyncfunctiongetUserPosts(userId) {returnfetchUser(userId)// 返回Promise.then(user=>fetchPosts(user.id)).then(posts=> {returnasyncFunction(posts);// 在then中调用async函数});}