js之异步编程四种方案(回调函数/promise/genarator/async)

本文详细介绍了如何避免回调地狱,通过Promise实现异步任务的顺序执行,包括回调函数的回调地狱问题、Promise的使用方法(单/多参数传递、错误处理、Promise.all()和Promise.race()),以及它们在解决并发控制中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

异步函数:各自执行各自的,互不干扰,互相之间也不会等待。

假设有三个人A, B, C一起接力跑,需要A跑到B的位置,B才能开始跑,B跑到C的位置,C才能开始跑

遇到多个异步函数需要按顺序执行该怎么做呢?

一、回调函数(回调地狱)

缺点:多个回调函数嵌套的时候会造成回调函数地狱,代码耦合度高,不直观,可维护差。

示例:

function run(next) {
    setTimeout(() => {
    	console.log('跑完一段')
        next();
    }, 6000);
}
run(function() {
    run(function(){
       setTimeout(() => {
           console.log('到终点啦!')
	   }, 6000);
    });
})

如上,任务多起来旧会形成很深的嵌套结构——称之回调地狱。
在这里插入图片描述

二、Promise链式调用

Promise 是异步编程的一种解决方案,避免了地狱回调,将嵌套的回调函数作为链式调用

2.1 步骤

1). 定义前一项任务时
返回一个promise:return new Promise(),并包裹原异步任务

function run(){
 	return new Promise((resolve) => {
	  	// 原异步任务
	  	setTimeout(() => {
	  		// 在原异步函数的最后一行代码,主动调用resolve
    		resolve()
	    }, 6000);
	}
  )
}

2). 使用.then连接前后两个异步任务:

任务1().then(任务2).then(任务3)

promise解决回调地狱代码:

function run(isFinal){
 	return new Promise((resolve) => {
	  	setTimeout(() => {
	  		console.log(isFinal ? '到终点啦!' : '跑完一段')
    		resolve()
	    }, 6000);
	}
  )
}

run()
	.then(run)
	.then(run(isFinal))
跑完一段
跑完一段
到终点啦!

2.2 Promise

a. promise参数

new Promise自带两个参数: .

  • resolve——通往.then
  • reject——通往.catch
前一项任务()
	.then(下一项任务)
	.then(...)
	.catch(
		function(错误提示信息){ 
			错误处理代码
		}
	)

示例:

function checkLogin(username, password) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (username === 'admin' && password === '123456') {
        resolve('登录成功'); // 成功时调用
      } else {
        reject('用户名或密码错误'); // 失败时调用
      }
    }, 1000);
  });
}

checkLogin('admin', '123456')
  .then(message => {
    console.log(message); // 成功时输出:"登录成功"
  })
  .catch(error => {
    console.log(error); // 输出:"用户名或密码错误"
  });

b. 实例的三大状态和两个过程

  • 异步任务执行过程中pending(挂起)
  • 异步任务成功执行完,通过resolve() 和reject()改变状态:
    • 调用Resolve(),切换为 fulfilled(已完成) 状态,new Promise()会自动调用.then()执行下一项任务
    • 调用reject(),切换为 rejected(已拒绝) 状态,new Promise()会自动调用.catch()执行错误处理代码

两个过程

  • pending -> fulfilled:Resolved
  • pending -> rejected:Rejected

一旦状态改变,就不会再变,任何时候都可以得到这个结果
判断题:对于一个向后台获取数据已经产生结果的promise:p1,再次调用p1.then,不会去重新发起请求获取数据

在这里插入图片描述

2.3 异步任务传参

a. 单个传参

利用resolve()进行传参,resolve默认只能传一个变量

示例:

function fetchData(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`用户${userId}的数据`);
    }, 1000);
  });
}

// 使用
fetchData(123)
  .then(data => {
    console.log(data); // "用户123的数据"
  });

b. 多个传参

那么如果我们想要传递多个参数呢?
可以放在数组或对象中传递:

示例:

function getUser(id) {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id, name: `用户${id}` }), 500);
  });
}

function getPosts(user) {
  return new Promise(resolve => {
    setTimeout(() => resolve(`${user.name}的文章列表`), 500);
  });
}

getUser(123)
  .then(user => {
    console.log(user); // { id: 123, name: "用户123" }
    return getPosts(user); // 传递user对象
  })
  .then(posts => {
    console.log(posts); // "用户123的文章列表"
  });

2.4 Promise.all()

  • 并行执行:所有Promise同时执行
  • 全部成功:只有当所有Promise都resolve时才会进入.then()
  • 失败处理:任意一个Promise reject就会立即进入.catch()
  • 结果顺序:结果数组顺序与输入Promise数组顺序一致
function fetchData(id, delay) {
  return new Promise(resolve => {
    setTimeout(() => resolve(`数据${id}`), delay);
  });
}

const request1 = fetchData(1, 1000); // 1秒后返回
const request2 = fetchData(2, 1500); // 1.5秒后返回
const request3 = fetchData(3, 500);  // 0.5秒后返回

Promise.all([request1, request2, request3])
  .then(results => {
    console.log('所有请求完成:', results); 
    // 输出: ["数据1", "数据2", "数据3"] (约1.5秒后同时显示)
  })
  .catch(error => {
    console.log('请求失败:', error);
  });

2.5 Promise.race()

  • 竞速机制:只关心最先完成的Promise
  • 短路特性:只要有一个Promise完成(无论resolve/reject)就立即返回
  • 结果类型:返回第一个完成的Promise的结果或错误
function fetchData(source, delay) {
  return new Promise(resolve => {
    setTimeout(() => resolve(`${source}数据`), delay);
  });
}

const apiRequest = fetchData("API", 800);
const cacheRequest = fetchData("缓存", 200);
const backupRequest = fetchData("备用服务", 1200);

Promise.race([apiRequest, cacheRequest, backupRequest])
  .then(firstResponse => {
    console.log("最先返回的结果:", firstResponse); 
    // 输出: "缓存数据" (约200ms后)
  })
  .catch(error => {
    console.log("错误:", error);
  });

三、generator

当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。
核心:考虑何时将函数的控制权转移回来
详见:ES6——generator与yield

四、async/await

详见:事件循环机制 + ES7:Async/Await(基于generator原理实现)附详细示例分析
async 基于Generator+ promise 实现的一个自动执行的语法糖,为了进一步优化then链而出现。

async 是“异步”,申明一个函数是异步的,await 则为等待,当执行到一个await语句的时候,如果语句返回一个promise 对象,那么函数将会等待promise 对象的状态变为resolve 后再继续向下执行。
(参照generator思想)
generator和async/await区别:

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await能够返回Promise的resolve/reject的值

async函数返回的是一个Promise对象

例如,async的函数会在这里帮我们隐式使用Promise.resolve(1)

async function test() {
    return 1   
}

等价于

function test() {
   return new Promise(function(resolve, reject) {
       resolve(1)
   })
}

优点: 类同步代码

传统 Promise 写法:

function getUser(userId) {
  return fetch(`/users/${userId}`);
}

function getOrders(userId) {
  return fetch(`/orders?user=${userId}`);
}

// 嵌套的 .then() 回调
getUser(123)
  .then(user => {
    return getOrders(user.id)
      .then(orders => {
        console.log('用户:', user);
        console.log('订单:', orders);
      });
  })
  .catch(error => {
    console.log('出错:', error);
  });

使用 async/await 改进后:

async function displayUserData() {
  try {
    const user = await getUser(123);     // 等待用户数据
    const orders = await getOrders(user.id); // 用用户ID等订单数据
    
    console.log('用户:', user);    // 像同步代码一样写
    console.log('订单:', orders);  // 层级保持扁平
  } catch (error) {
    console.log('出错:', error);   // 统一错误处理
  }
}

displayUserData();
  • 调试友好
  • 错误处理更简单
    用 try/catch 即可捕获所有错误(包括同步错误)
  • 逻辑更直观
    消除了 .then() 嵌套,顺序执行异步操作,像同步代码一样自然
  • 轻松传值
    Promise 传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法

总结

  • promiseasync/await专门用于处理异步操作;generator并不是,他还有其他功能(对象迭代,部署Interator接口等)
  • promise编写代码比async/await复杂,可读性差
  • async为genarator语法糖,自动执行,使用上最简洁,同步编写代码,是异步编程的最终方案
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值