Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Promise 向左,Async/Await 向右?

Promise 向左,Async/Await 向右?

作者头像
用户1097444
发布于 2022-06-29 07:06:17
发布于 2022-06-29 07:06:17
51200
代码可运行
举报
运行总次数:0
代码可运行

1. 前言

从事前端开发至今,异步问题经历了 Callback Hell 的绝望,Promise/Deffered 的规范混战,到 Generator 的所向披靡,到如今 Async/Await 为大众所接受,这其中 Promise 和 Async/Await 依然活跃代码中,对他们的认识和评价也经历多次反转,也有各自的拥趸,形成了一直延续至今的爱恨情仇,其背后的思考和启发,依旧值得我们深思。

预先声明:

本文的目标并不是引发大家的论战,也不想去推崇其中任何一种方式来作为前端异步的唯一最佳实践,想在介绍下 Promise 和 Async/Await 知识和背后的趣事的基础上,探究下这些争议下隐藏的共识。

2. Promise

Promise 是异步编程的一种解决方案,相对于传统的回调地狱更加合理和强大。

所谓 Promise,简单来说就是一个容器,里面存储个未来才会结束的时间的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。其内部状态如下:

状态之间的流转是不可逆的,代码书写如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function httpPromise(): Promise<{ success: boolean; data: any }> {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        resolve({ success: true, data: {} });
      }, 1000);
    } catch (error) {
      reject(error);
    }
  });
}
httpPromise().then((res) => {}).catch((error) => {}).finally(() => {});

从语法角度上看,更加容易理解,但是美中不足的就是不够简洁,无法断点,冗余的匿名函数。

2.1. Promise 是如何实现的?

在刚入行的时候也去研究过《如何实现一个 Promise》这个课题,尝试写了下如下的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class promise {
    constructor(handler) {
        this.resolveHandler = null;
        this.rejectedHandler = null;
        setTimeout(() => {
            handler(this.resolveHandler, this.rejectedHandler);
        }, 0);
    }

    then(resolve, reject) {
        this.resolveHandler = resolve;
        this.rejectedHandler = reject;
        returnthis;
    }
}
function getPromise() {
    return new promise((resolve, reject) => {
        setTimeout(() => {
            resolve(20);
        }, 1000);
    });
}
getPromise().then((res) => {
    console.log(res);
}, (error) => {
    console.log(error);
});

虽然羞耻,但是不得不说当时还是挺满足的,后面发现无法解决异步注册的问题。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const promise1 = getPromise();
setTimeout(() => {
    promise1.then((data) => {
        console.log(data);
    }).catch((error) => {
        console.error(error);
    });
}, 0);

function getFPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(20), 1000);
    });
}
// 执行情况 报错
// Uncaught TypeError: promise1.then(...).catch is not a function
// Uncaught TypeError: resolve is not a function

// vs 官方Promise实现
const promise2 = getFPromise();
setTimeout(() => {
    promise2.then((data) => {
        console.log(data);
    }).catch((error) => {
        console.error(error);
    });
}, 0);
// 执行情况,符合预期
// 20

对于这一部分有兴趣的同学可以自行查找标准版的实现方案,不过在这个探索过程中确实勾起对基础知识的兴趣,这也是本文去挖掘这部分知识的初衷。

接下来看看 Async/Await 吧。

3. Async/Await

Async/Await 并不是什么新鲜的概念,事实的确如此。

早在 2012 年微软的 C# 语言发布 5.0 版本时,就正式推出了 Async/Await 的概念,随后在 Python 和 Scala 中也相继出现了 Async/Await 的身影。再之后,才是我们今天讨论的主角,ES 2016 中正式提出了 Async/Await 规范。

以下是一个在 C# 中使用 Async/Await 的示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public async Task<int> SumPageSizesAsync(IList<Uri> uris)
{
    int total = 0;
    foreach (var uri in uris) {
        statusText.Text = string.Format("Found {0} bytes ...", total);
        var data = await new WebClient().DownloadDataTaskAsync(uri);
        total += data.Length;
    }
    statusText.Text = string.Format("Found {0} bytes total", total);
    return total;
}

再看看在 JavaScript 中的使用方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function httpRequest(value) {
  const res = await axios.post({ ...value });
  return res;
}

好的设计总是会想借鉴的,不寒碜。

其实在前端领域,也有不少类 Async/Await 的实现,其中不得不提到的就是知名网红之一的老赵写的 wind.js,站在今天的角度看,windjs 的设计和实现不可谓不超前。

3.1. Async/Await 是如何实现的?

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

这里引用阮一峰老师的描述:

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

前文有一个 Generator 函数,依次读取两个文件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

上面代码的函数 gen 可以写成 async 函数,就是下面这样。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

相对于 Generator 的改进主要集中集中在:

  • 内置执行器
  • 更好的语义化
  • Promise 的返回值

到这里大家会发现,Async/Await 本质也是 Promise 的语法糖:Async 函数返回了 Promise 对象。

来看下实际 Babel 转化的代码,方便大家理解下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function test() {
  const img = await fetch('tiger.jpg');
}

// babel 转换后
'use strict';

var test = function() {
    var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
        var img;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case0:
                        _context.next = 2;
                        return fetch('tiger.jpg');

                    case2:
                        img = _context.sent;

                    case3:
                    case'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function test() {
        return _ref.apply(this, arguments);
    };
}();

function _asyncToGenerator(fn) {
    return function() {
        var gen = fn.apply(this, arguments);
        return new Promise(function(resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function(value) {
                        step("next", value);
                    }, function(err) {
                        step("throw", err);
                    });
                }
            }
            return step("next");
        });
    };
}

不难看出最终还是转换成基于 Promise 的调用,但是本来的三行代码被转换成 52 行代码,在一些场景下就带来了成本。

例如 Vue3 并没有采用?.(可选链式操作符符号)的原因:

虽然使用?很简洁,但是实际打包下反而更加冗余了,增加了包的体积,影响 Vue3 的加载速度,这也是 Async/Await 这类简洁语法的痛点。

暂时不考虑深层次的运行性能,仅仅考虑代码使用方式来看,Async/Await 是否完美无缺?

以 “请求 N 次重试” 的实现为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @description: 限定次数来进行请求
 * @example: 例如在5次内获取到结果
 * @description: 核心要点是完成tyscript的类型推定,其次高阶函数
 * @param T 指定返回数据类型,M指定参数类型
 */

export default function getLimitTimeRequest<T>(task: any, times: number) {
  // 获取axios的请求实例
  let timeCount = 0;
  async function execTask(resolve, reject, ...params: any[]): Promise<void> {
    if (timeCount > times) {
      reject(newError('重试请求失败'));
    }
    try {
      const data: T = await task(...params);
      if (data) {
        resolve(data);
      } else {
        timeCount++;
        execTask(resolve, reject, params);
      }
    } catch (error) {
      timeCount++;
      execTask(resolve, reject, params);
    }
  }
  return function <M>(...params: M[]): Promise<T> {
    return new Promise((resolve, reject) => {
      execTask(resolve, reject, ...params);
    });
  };
}

常见的实现思路是将 Promise 的 Resolve、Reject 的句柄传递到迭代函数中,来控制 Promise 的内部状态转化,那如果用 Async/Await 如何做?很明显并不好做,暴露了它的一些不足:

  • 缺少复杂的控制流程,如 always、progress、pause、resume 等
  • 内部状态无法控制,错误捕获严重依赖 try/catch
  • 缺少中断的方法,无法 abort

当然,站在 EMCA 规范的角度来看,有些需求可能比较少见,但是如果纳入规范中,也可以减少前端程序员在挑选异步流程控制库时的纠结了。

4. 总结

针对前端异步的处理方案,Promise 和 Async/Await 都是优秀的处理方案,但是美中不足的是有一定的不足,随着前端工程化的深入,一定会有更好的方案来迎合解决这些问题,大家不要失望,未来还是可期的。

从 Promise 和 Async/Await 的演进和纠结中,大家实际能够感到前端人对 JavaScript 世界的辛苦耕作和奇思妙想,这种思维和方式也可以沉淀到我们日常的需求开发中去,善于求索,辩证的去使用它们,追求更加极致的方案。

紧追技术前沿,深挖专业领域

扫码关注我们吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
跨越时空的对白——async&await分析
在ES6中新增了asgnc...await...的异步解决方案,对于这种方案,有多种操作姿势,比如这样
Yerik
2022/05/08
1.2K3
跨越时空的对白——async&await分析
Javascript异步回调细数:promise yield async/await
虽然我对js的鄙视一直都是无以复加,但是奈何前端环境不得不依赖javascript。哪些nodejs的大神们四处布道nodejs统治一切:单线程非阻塞,高IO操作。但是,java也可以做好吧,而且GO做的更干练!假设你的应用程序要做两件事情,分别是A和B。你发起请求A,等待响应,出错。发起请求B,等待响应,出错。Go语言的阻塞模型可以非常容易地处理这些异常,而换到了Node里,要处理异常就要跳到另一个函数里去,事情就会变得复杂。
周陆军博客
2023/05/07
8720
Promise解决回调嵌套问题及终极解决方案async 和 await
目的: promise是书写异步代码的另一种方式, 解决回调函数嵌套的问题 1.如何创建一个 promise 对象
青梅煮码
2023/03/02
2.4K0
JavaScript异步编程:Promise、async&await与Generator
Promise 是 JavaScript 中用于处理异步操作的一种解决方案,它提供了一种更简洁、更清晰的方式来处理异步操作的结果。Promise 有三种状态:pending(进行中)、fulfilled(已完成,成功)和 rejected(已完成,失败)。Promise 的核心概念是链式调用,通过 .then() 方法处理成功(fulfilled)的结果,或者通过 .catch() 方法处理失败(rejected)的结果。
iwhao
2024/07/04
2841
ES6--Promise、Generator及async
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
奋飛
2019/08/15
7050
node.js的async和await
ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async其实本质是Generator函数的语法糖
雪山飞猪
2019/08/29
1.7K0
优雅的异步编程版本答案async和await解析
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
henu_Newxc03
2021/12/26
5190
如何写出一个惊艳面试官的 Promise【近 1W字】 前言源码1.Promise2.Generator3.async 和 await4.Pro
1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码); 2.在写之前我们简单回顾下他们的作用; 3.手写模块见PolyFill.
火狼1
2020/05/09
7180
如何写出一个惊艳面试官的 Promise【近 1W字】
                            前言源码1.Promise2.Generator3.async 和 await4.Pro
「Async/Await」仅仅了解使用?这次我们来聊聊它是如何被实现的
这篇 Async 是如何被实现的,其实断断续续已经在草稿箱里躺了很久了。终于在一个夜黑风高的周六晚上可以给他画上一个句号。
19组清风
2022/02/28
8270
「Async/Await」仅仅了解使用?这次我们来聊聊它是如何被实现的
promise & axios & async_await 关于 Promise
Promise 是ES6里面异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。 简单说Promise 就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 有三种状态,pending(进行中)、resolved(已完成)、rejected(已失败),特点是只有异步操作的结果,可以决定当前是哪一种状态,状态一旦改变,就无法再次改变状态;
前端小tips
2021/11/24
1.5K0
promise & axios & async_await 关于 Promise
手写async await的最简实现(20行)
如果让你手写async函数的实现,你是不是会觉得很复杂?这篇文章带你用20行搞定它的核心。
ssh_晨曦时梦见兮
2020/04/11
1.5K0
JavaScript 的 async/await : async 和 await 在干什么
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。 await 只能出现在 async 函数中。
一个会写诗的程序员
2018/08/17
9840
JavaScript 的 async/await : async 和 await 在干什么
async/await剖析
JavaScript是单线程的,为了避免同步阻塞可能会带来的一些负面影响,引入了异步非阻塞机制,而对于异步执行的解决方案从最早的回调函数,到ES6的Promise对象以及Generator函数,每次都有所改进,但是却又美中不足,他们都有额外的复杂性,都需要理解抽象的底层运行机制,直到在ES7中引入了async/await,他可以简化使用多个Promise时的同步行为,在编程的时候甚至都不需要关心这个操作是否为异步操作。
WindRunnerMax
2020/08/27
3370
Promise、Generator、Async 合集
我们知道Promise与Async/await函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到Promise处理异步,到Generator处理异步,再到Async/await处理异步,每一次的技术更新都使得JavaScript处理异步的方式更加优雅,从目前来看,Async/await被认为是异步处理的终极解决方案,让JS的异步处理越来越像同步任务。异步编程的最高境界,就是根本不用关心它是不是异步。
泯泷、
2024/03/09
1430
async 函数
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
小小杰啊
2022/12/21
1K0
JavaScript 异步解决方案 async/await
异步操作一直都是 JavaScript 中一个比较麻烦的事情,从最早的 callback hell,到TJ大神的 co,再到 Promise 对象,然后ES6中的 Generator 函数,每次都有所改进,但都不是那么彻底,而且理解起来总是很复杂。
李振
2021/11/26
4080
async/await 源码实现
如果你有一个这样的场景,b依赖于a,c依赖于b,那么我们只能通过promise then的方式实现。这样的的可读性就会变得很差,而且不利于流程控制,比如我想在某个条件下只走到 b 就不往下执行 c 了,这种时候就变得不是很好控制!
用户4131414
2020/03/19
1.3K0
Promise与Async/Await:异步编程的艺术
一个Promise对象代表了一个现在、将来或永远可能可用,也可能不可用的值。它有三种状态:pending(进行中)、fulfilled(已成功)或rejected(已失败)。
空白诗
2024/06/14
2040
JavaScript 中回调、Promise 和 Async/Await 的代码案例
本文将通过代码示例展示如何使用基于回调的 API,然后将其改成使用 Promises,最后再用 Async/Await 语法。本文不会详细解释回调、promise 和 Async/Await 语法。有关这些概念的详细解释,请查看 MDN 的 Asynchronous JavaScript[1],它解释了什么是异步性以及如何用回调、promise 和 Async/Await 语法处理异步 JavaScript。
疯狂的技术宅
2021/06/09
1.5K0
JavaScript 中回调、Promise 和 Async/Await 的代码案例
Promise 和 Async/Await的区别
如果你正在阅读这篇文章,你可能已经理解了 promise 和 async/await 在执行上下文中的不同之处。
前端小智@大迁世界
2024/02/12
3940
Promise 和 Async/Await的区别
推荐阅读
相关推荐
跨越时空的对白——async&await分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验