前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Promise 向左,Async/Await 向右?

Promise 向左,Async/Await 向右?

作者头像
用户1097444
发布2022-06-29 15:06:17
4700
发布2022-06-29 15:06:17
举报
文章被收录于专栏:腾讯IMWeb前端团队

1. 前言

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

预先声明:

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

2. Promise

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

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

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

代码语言:javascript
复制
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
复制
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
复制
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
复制
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
复制
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
复制
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
复制
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
复制
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
复制
/**
 * @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 删除。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. Promise
    • 2.1. Promise 是如何实现的?
    • 3. Async/Await
      • 3.1. Async/Await 是如何实现的?
      • 4. 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档