0

0

如何让一个异步函数既能被 await 也能直接调用

冷炫風刃

冷炫風刃

发布时间:2026-01-22 20:11:08

|

531人浏览过

|

来源于php中文网

原创

异步函数默认返回promise,支持await;若需同步取值,应通过缓存机制(如闭包缓存promise结果)或自定义promise子类实现,而非强行同步等待。

如何让一个异步函数既能被 await 也能直接调用

一个异步函数默认返回 Promise,所以它天然支持 await;但“直接调用”(即不加 await)时,你拿到的是 Promise 对象本身,不是最终值——这不是“不能直接调用”,而是调用后行为不同。真正想问的通常是:如何让一个函数既能 await fn() 获取结果,又能在同步上下文中像普通函数一样使用(比如 fn() 直接返回值)?

核心思路:返回一个“可 await 也可同步取值”的对象

JavaScript 中无法让一个函数同时是同步的又返回 Promise,但可以通过封装返回一个自定义对象,让它:

  • 作为 Promise 被 await(实现 thencatch 等)
  • 提供同步读取结果的方法(如 .value.unwrap()),但需确保调用时 Promise 已完成
  • 或更实用的方式:用 async 函数 + 包装器,配合状态缓存(适合幂等、可缓存的场景)

方案一:返回带 .then 的 Promise,并缓存结果(推荐用于初始化/配置类场景)

适用于执行一次、结果可复用的异步操作(如加载配置、连接数据库)。通过闭包缓存 Promise 和最终值:

function createAsyncValue(asyncFn) {
  let promise = null;
  let result = undefined;
  let error = undefined;
  let resolved = false;

  return function() {
    if (!promise) {
      promise = asyncFn().then(
        (val) => { result = val; resolved = true; return val; },
        (err) => { error = err; throw err; }
      );
    }
    // 返回同一个 Promise,支持 await
    const p = promise;
    // 扩展 Promise 实例,添加同步访问能力(仅当已完成)
    if (resolved) {
      p.value = result;
      p.unwrap = () => result;
      p.isResolved = true;
    } else {
      p.isResolved = false;
      p.unwrap = () => { throw new Error('Promise not settled yet'); };
    }
    return p;
  };
}

// 使用示例
const fetchUser = createAsyncValue(() => fetch('/api/user').then(r => r.json()));

// ✅ 可 await
const user = await fetchUser();

// ✅ 也可直接调用,后续调用能同步取值(前提是已 resolve 过)
if (fetchUser().isResolved) {
  console.log(fetchUser().value); // 不再发起请求,直接拿缓存值
}

方案二:返回 Promise 子类(高级,需谨慎)

继承 Promise,重写 then 并添加同步属性(如 .value)。但注意:Promise 构造器内部状态不可直接暴露,必须靠 .then 回调捕获结果,因此 .value 只能在 resolve 后安全读取:

拍我AI
拍我AI

AI视频生成平台PixVerse的国内版本

下载
class AsyncValue extends Promise {
  constructor(executor) {
    let _resolve, _reject;
    super((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
      executor(resolve, reject);
    });

    this._value = undefined;
    this._error = undefined;
    this._settled = false;

    const originalThen = this.then.bind(this);
    this.then = (onFulfilled, onRejected) => {
      const wrappedFulfilled = (val) => {
        this._value = val;
        this._settled = true;
        return onFulfilled?.(val);
      };
      const wrappedRejected = (err) => {
        this._error = err;
        this._settled = true;
        return onRejected?.(err);
      };
      return originalThen(wrappedFulfilled, wrappedRejected);
    };

    Object.defineProperty(this, 'value', {
      get() {
        if (!this._settled) throw new Error('Not settled yet');
        if (this._error) throw this._error;
        return this._value;
      }
    });
  }
}

// 创建函数
function asyncFn() {
  return new AsyncValue((resolve) =>
    setTimeout(() => resolve('done'), 100)
  );
}

// ✅ await 正常工作
console.log(await asyncFn()); // 'done'

// ✅ 同步取值(仅在 resolve 后)
const p = asyncFn();
setTimeout(() => {
  console.log(p.value); // 'done' —— 成功读取
}, 200);

方案三:不推荐的做法——试图“同步等待”

不要用 Atomics.waitwhile(!done)eval(`await ${...}`) 等方式强行同步阻塞。这会冻结主线程、破坏异步模型,违背 JavaScript 设计原则,在浏览器中尤其危险。

如果你的业务逻辑**必须同步执行**,说明设计上应重新评估:是否本就不该是异步?能否把异步前置(如启动时预加载),后续全部走同步路径?

本质上,JavaScript 的异步本质决定了“真正同步取值”和“原生 await 支持”无法在单个裸函数上无缝共存。最自然、健壮的做法是:明确区分场景——该 await 的地方就 await,需要同步值的地方,确保它来自已 resolve 的缓存或初始化完成的状态。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

106

2023.09.25

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

151

2025.07.29

promise的用法
promise的用法

“promise” 是一种用于处理异步操作的编程概念,它可以用来表示一个异步操作的最终结果。Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。Promise的用法主要包括构造函数、实例方法(then、catch、finally)和状态转换。

336

2023.10.12

html文本框类型介绍
html文本框类型介绍

html文本框类型有单行文本框、密码文本框、数字文本框、日期文本框、时间文本框、文件上传文本框、多行文本框等等。详细介绍:1、单行文本框是最常见的文本框类型,用于接受单行文本输入,用户可以在文本框中输入任意文本,例如用户名、密码、电子邮件地址等;2、密码文本框用于接受密码输入,用户在输入密码时,文本框中的内容会被隐藏,以保护用户的隐私;3、数字文本框等等。

427

2023.10.12

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

384

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2111

2023.08.14

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号