原来Python和TypeScript/JavaScript 中的await的作用是一样的

32 阅读7分钟

Python 和 TypeScript/JavaScript(后文统称 TS/JS)的 await 核心设计目标完全一致 ——简化异步代码的线性书写,避免回调地狱,但因语言运行时、异步模型的差异,在「原理实现、语法约束、使用场景细节」上存在明显区别。以下从「原理→用法→场景」三个维度,结合代码示例对比异同:

一、核心共识(相同点)

无论 Python 还是 TS/JS,await 的核心逻辑和目标完全一致:

  1. 作用对象:仅能在 async 函数内使用,且只能等待「异步任务载体」(Python 是协程 / Task/Future,TS/JS 是 Promise);
  2. 核心行为:暂停当前 async 函数的执行,释放执行权(不阻塞主线程 / 事件循环),待异步任务完成后,恢复当前函数继续执行后续代码;
  3. 错误处理:均支持用 try/catch 捕获 await 任务的异常(如网络错误、超时);
  4. 设计目标:替代嵌套回调(回调地狱),让异步代码像同步代码一样线性书写,提升可读性和维护性。

相同用法示例(对比)

Python(等待协程任务)
import asyncio

# 异步任务(协程)
async def fetch_data():
    await asyncio.sleep(1)  # 模拟 IO 等待
    return "数据"

# async 函数内使用 await
async def main():
    try:
        result = await fetch_data()  # 暂停 main,等待 fetch_data 完成
        print(result)  # 任务完成后恢复执行
    except Exception as e:
        print("错误:", e)

asyncio.run(main())  # 启动事件循环
TS/JS(等待 Promise)
// 异步任务(Promise)
function fetchData(): Promise<string> {
  return new Promise(resolve => setTimeout(() => resolve("数据"), 1000));
}

// async 函数内使用 await
async function main() {
  try {
    const result = await fetchData();  // 暂停 main,等待 Promise 完成
    console.log(result);  // 任务完成后恢复执行
  } catch (e) {
    console.error("错误:", e);
  }
}

main();  // 直接执行,依赖事件循环

二、关键差异(不同点)

1. 原理层面:异步模型与执行载体

这是最核心的差异,决定了两者的调度逻辑和并发能力:

维度PythonTS/JS
异步模型单线程事件循环 + 显式协程(asyncio单线程事件循环 + 隐式协程(Promise 封装)
任务载体协程(coroutine)、Task、FuturePromise 对象(async 函数返回 Promise)
调度方式协作式调度(必须通过 await/yield 主动释放执行权,否则阻塞事件循环)协作式调度(await 自动释放执行权,Promise 状态变更触发回调)
并发能力支持多协程并发(需通过 asyncio.create_task() 显式创建任务)支持多 Promise 并发(调用异步函数即创建 Promise,自动加入事件循环)

关键说明:

  • Python 的协程是「显式的」:async def 定义的函数仅返回协程对象,需通过 asyncio.create_task() 或 gather() 加入事件循环才会执行;若协程内无 await,会一直占用事件循环,阻塞其他任务。
  • TS/JS 的协程是「隐式的」:async 函数调用后直接返回 Promise,自动加入事件循环等待执行;await 本质是 Promise 的语法糖,底层通过事件循环的微任务队列调度,无需手动管理任务。

反例对比(阻塞事件循环):

Python(无 await 的协程阻塞事件循环)
import asyncio

async def blocking_task():
    sum = 0
    for i in range(100000000):  # 纯 CPU 计算,无 await 释放执行权
        sum += i
    print("阻塞任务完成")

async def main():
    # 同时创建两个任务,但 blocking_task 无 await,会阻塞事件循环
    task1 = asyncio.create_task(blocking_task())
    task2 = asyncio.create_task(asyncio.sleep(1))  # 本应 1 秒后执行,但被阻塞
    await task1
    await task2

asyncio.run(main())  # 输出:阻塞任务完成(约 1-2 秒后)→ 无其他输出(task2 被阻塞到 task1 完成)
TS/JS(无 await 的 async 函数不阻塞)
async function blockingTask() {
  let sum = 0;
  for (let i = 0; i < 100000000; i++) sum += i;  // 纯 CPU 计算,无 await
  console.log("阻塞任务完成");
}

async function main() {
  // 调用 async 函数即创建 Promise,自动加入事件循环
  const task1 = blockingTask();  // 同步执行 CPU 计算(阻塞主线程)
  const task2 = new Promise(resolve => setTimeout(() => resolve("任务2完成"), 1000));
  await task1;
  await task2;
}

main();  // 输出:阻塞任务完成(约 1-2 秒后)→ 任务2完成(无阻塞,因 setTimeout 是宏任务)

(注:TS/JS 的纯 CPU 计算会阻塞主线程,但事件循环的宏任务 / 微任务机制仍会在计算完成后执行队列中的任务,与 Python 的事件循环阻塞逻辑不同)。

2. 用法层面:语法约束与并发 API

维度PythonTS/JS
并发 APIasyncio.create_task()(创建并发任务)、asyncio.gather()(批量等待)、asyncio.wait()(灵活控制)Promise.all()(批量等待,一个失败则整体失败)、Promise.allSettled()(等待所有完成,保留成功 / 失败结果)、Promise.race()(取第一个完成的任务)
任务启动时机协程需显式通过 create_task() 启动才会并发;直接 await 协程 会串行执行异步函数调用即启动任务(返回 Promise),无需显式启动;直接 await Promise 若先创建多个 Promise 再 await,仍可并发
取消任务支持任务取消(task.cancel()),可通过 try/except CancelledError 捕获不支持原生 Promise 取消(需通过第三方库如 bluebird,或手动实现取消逻辑)
顶层 await 支持Python 3.7+ 支持(模块级 awaitES2022+ 支持(模块级 await,浏览器 / Node.js 需开启对应支持)

并发用法对比(核心差异点)

Python(需显式创建 Task 实现并发)
import asyncio

async def task1():
    await asyncio.sleep(2)
    return "任务1"

async def task2():
    await asyncio.sleep(2)
    return "任务2"

async def main():
    # 方式 1:显式创建 Task 并发(推荐)
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())
    result1 = await t1
    result2 = await t2
    print(result1, result2)  # 总耗时 ≈ 2 秒

    # 方式 2:用 gather 批量并发(更简洁)
    result1, result2 = await asyncio.gather(task1(), task2())
    print(result1, result2)  # 总耗时 ≈ 2 秒

asyncio.run(main())
TS/JS(创建 Promise 即并发,无需显式启动)
async function task1(): Promise<string> {
  await new Promise(resolve => setTimeout(resolve, 2000));
  return "任务1";
}

async function task2(): Promise<string> {
  await new Promise(resolve => setTimeout(resolve, 2000));
  return "任务2";
}

async function main() {
  // 方式 1:先创建 Promise(启动任务),再 await 并发
  const p1 = task1();
  const p2 = task2();
  const result1 = await p1;
  const result2 = await p2;
  console.log(result1, result2);  // 总耗时 ≈ 2 秒

  // 方式 2:用 Promise.all 批量并发(更简洁)
  const [result1, result2] = await Promise.all([task1(), task2()]);
  console.log(result1, result2);  // 总耗时 ≈ 2 秒
}

main();

3. 场景层面:适用场景与生态差异

维度PythonTS/JS
主要适用场景后端 IO 密集型任务(异步接口、异步爬虫、异步数据库操作)、CLI 工具前端异步操作(AJAX 请求、DOM 事件、定时器)、Node.js 后端 IO 密集型任务
生态工具asyncio(标准库)、aiohttp(异步 HTTP)、asyncpg(异步 PostgreSQL)、FastAPI(异步 Web 框架)fetch/axios(HTTP 请求)、Node.js fs.promises(异步文件操作)、express/nestjs(Node.js 异步框架)
CPU 密集型任务处理不适合(单线程事件循环易阻塞,需通过 multiprocessing 多进程规避)不适合(单线程主线程易阻塞,需通过 Web Worker(前端)/child_process(Node.js)规避)
跨平台支持主要用于服务器 / 桌面端,移动端支持较弱(需通过 Kivy 等框架)前端(浏览器)、后端(Node.js)、移动端(React Native/Weex)全平台支持

三、总结:异同核心对照表

对比维度相同点不同点
核心行为暂停当前 async 函数,释放执行权,完成后恢复Python 需显式创建 Task 并发;TS/JS 调用异步函数即并发
任务载体均依赖异步任务载体Python:协程 / Task/Future;TS/JS:Promise
调度机制协作式调度(依赖 await 释放执行权)Python 无 await 会阻塞事件循环;TS/JS 无 await 阻塞主线程但不影响事件循环队列
并发 API支持批量并发与等待Python:asyncio.gather()/create_task();TS/JS:Promise.all()/allSettled()
错误处理均支持 try/catch 捕获异常-
适用场景均适合 IO 密集型任务,不适合 CPU 密集型Python 侧重后端;TS/JS 侧重全栈(前端 + Node.js 后端)

一句话概括:

await 的「核心设计思想(线性化异步代码)」完全一致,但「底层执行模型(显式协程 vs 隐式 Promise)」和「并发管理方式(显式 Task vs 自动 Promise)」的差异,是由语言的生态定位(Python 后端 vs TS/JS 全栈)决定的。实际开发中,只需记住:

  • Python 用 await 必须配合 asyncio 调度,并发需显式创建 Task;
  • TS/JS 用 await 直接配合 Promise,并发只需先创建多个 Promise 再统一等待。