1. 首页

【TypeScript 进化史 — 5】将 async、await 编译到 ES3、ES5 (外部帮助库)

自2015年11 发布1.7版以来,TypeScript 已支持 async/await 关键字。编译器使用 yield 将异步函数转换为生成器函数。这意味着咱们无法针对 ES3 或 ES5,因为生成器仅在 ES6 中引入的。

TypeScript 2.1 现在支持将异步函数编译为 ES3 和 ES5。与生成的其余代码一样,它们在所有 JS 环境中运行。(这甚至包括IE6,当然不建议在去兼容这么古老的浏览器了)

使用异步函数

下面是一个简单的函数,它在给定的毫秒数之后解析一个 Promise 。使用内置的 setTimeout 函数在 ms 毫秒过后调用 resolve 回调:

function delay(ms: number) {
  return new Promise<void>(function(resolve) {
    setTimeout(resolve, ms)
  })
}
//JS中文网 - 前端进阶资源分享 [www.javascriptC.com](https://www.javascriptc.com/)

delay 函数返回一个 promise,调用时可以使用 await 来等待这个 promise,如下所示:

function delay(ms: number) {
  return new Promise<void>(function(resolve) {
    setTimeout(resolve, ms);
  })
}

async function asyncAwait () {
  console.log('开始执行...');

  await delay(1000);

  console.log('1 秒过后')

  await delay(1000);

  console.log('过 2 秒后执行完成');
}

现在,如果调用 asyncAwait 函数,在控制台会看到三个消息,一个接一个,每个消息之间都有一个暂停。

asyncAwait();
// 开始执行...
// 1 秒过后
// 过 2 秒后执行完成

现在,来看一下针对 ES2017,ES2016/ES2015 和 ES5/ES3 时 TypeScript 生成的 JS 代码。

编译 async/await 到 ES2017

异步函数是一种JavaScript语言功能,在 ES2017 中进行标准化。

因此,在面向 ES2017 时,TypeScript 编译器无需将 async/await 重写为其他某种构造,因为两个异步函数均已被原生支持。生成的 JS 代码与 TypeScript 代码相同,除了已除去所有类型注释和空白行:

function delay(ms) {
  return new Promise(function(resolve) {
    setTimeout(resolve, ms);
  })
}

async function asyncAwait () {
  console.log('开始执行...');

  await delay(1000);

  console.log('1 秒过后')

  await delay(1000);

  console.log('过 2 秒后执行完成');
}

这里没什么可说的,这是咱们自己编写的代码,只是没有类型注释。

编译 async/await 到 ES2015/ES2016

针对 ES2015,TypeScript 编译器使用生成器函数和 yield 关键字重写 async/await。它还会生成__awaiter 帮助方法作为异步函数的运行程序。以上 asyncAwait 函数的结果编译成 JS 代码如下所示:

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments)).next());
    });
};
function delay(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function asyncAwait() {
    return __awaiter(this, void 0, void 0, function* () {
        console.log('开始执行...');
        yield delay(1000);
        console.log('1 秒过后');
        yield delay(1000);
        console.log('过 2 秒后执行完成');
    });
}

辅助代码的 generated 可以忽略不计,但是也不错。如果想在 Node 6.x 或 7.x 应用程序中使用 async/await,需要的配置中设置targetES2015ES2016

请注意,ES2016 标准化的惟一特性是求幂运算符和 Array.prototype.includes 方法,这里两个方法都不使用。因此,针对 ES2016 生成的 JS 代码与针对 ES2015 生成的代码相同。

编译 async/await 到 ES3/ES5

有趣的地方是,使用 TypeScript 2.1,可以让编译器将异步函数降级到 ES3 或 ES5,下面是咱们之前的例子:

//JS中文网 - 前端进阶资源分享 [www.javascriptC.com](https://www.javascriptc.com/)
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
function delay(ms) {
    return new Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function asyncAwait() {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    console.log('开始执行...');
                    return [4 /*yield*/, delay(1000)];
                case 1:
                    _a.sent();
                    console.log('1 秒过后');
                    return [4 /*yield*/, delay(1000)];
                case 2:
                    _a.sent();
                    console.log('过 2 秒后执行完成');
                    return [2 /*return*/];
            }
        });
    });
}
asyncAwait();

里面有很多帮助代码。除了前面已经看到的 __awaiter 函数之外,编译器还注入了另一个名为generator的帮助函数,它使用一个状态机来模拟一个可以暂停和恢复的生成器函数。

注意,为了让各位的代码在 ES3 或 ES5 环境中成功运行,需要提供Promise polyfill,因为 Promise 只在 ES2015 中引入。另外,你必须让TypeScript知道在运行时,它可以找到 Promise 函数。这在上一章TypeScript 2.0:内置类型声明 有讲过了。

有了它,async/await 在所有 JS 引擎中都可以运行。

接下来,来看看如何避免在编译中的每个 TypeScript 文件一遍又一遍地将这些辅助函数写入。

TypeScript 中的外部帮助库

在某些情况下,TypeScript 编译器会将帮助函数注入到在运行时调用的生成输出代码中。每个这样的帮助函数都模拟编译目标(target) (ES3、ES5、ES2015) 本身不支持的特定语言特性的语义。

目前,TypeScript 中有以下帮助函数

  • __extends 用于继承
  • __assign 用于扩展对象属性
  • _rest 用于表示对象的剩余属性
  • 还有一些装饰器 __decorate, __param, __metadata
  • __awaiter__generator 用于 async/await

带有 extends 的 ES6 类的典型用例是如下所示的 React 组件:

import * as React from "react";

export default class FooComponent extends React.Component<{}, {}> {
  render() {
    return <div>Foo</div>;
  }
}

如果配置的 target 是ES5,那么 TypeScript 编译器将生成(emit )以下 JS 代码,其中既不支持 class ,也不支持 extends

"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var React = require("react");
var FooComponent = (function (_super) {
    __extends(FooComponent, _super);
    function FooComponent() {
        return _super.apply(this, arguments) || this;
    }
    FooComponent.prototype.render = function () {
        return (React.createElement("div", null, "Foo"));
    };
    return FooComponent;
}(React.Component));
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = FooComponent;

虽然这种方法对于这样的简单示例很有效,但是它有一个很大的缺点:将 __extends 帮助函数代码注入到使用带有extends语句的类的每个编译文件中。也就是说,为应用程序中每个基于类的 React 组件触发帮助函数。

对于一个包含数十个或数百个 React 组件的中型应用程序,对于__extends 函数来说是大量重复的代码。大量重复的代码也会增加包大小,所以下载时间也会随之增加。

这个问题只会对于其它的帮助的函数也会存在,如开头讲的如何将 async/await 降级到 ES3/ES5 中的 __awaiter__generator 帮助函数也很大。注意,它们被注入到每个使用 async/await 关键字的文件中:

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments)).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t;
    return { next: verb(0), "throw": verb(1), "return": verb(2) };
    function verb(n) { return function (v) { return step([n, v]); }; }
    //JS中文网 - 前端进阶资源分享 [www.javascriptC.com](https://www.javascriptc.com/)
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [0, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};

--noEmitHelpers 标志

在 1.5 版中,TypeScript 增加 --noEmitHelpers 标志。当指定此编译器选项时,TypeScript 不会在编译后生成任何帮助函数。这样,捆绑包的大小会减少很多。

下面是之前的 React 组件,用 --noEmitHelpers 标志编译:

"use strict";
var React = require("react");
var FooComponent = (function (_super) {
    __extends(FooComponent, _super);
    function FooComponent() {
        return _super.apply(this, arguments) || this;
    }
    FooComponent.prototype.render = function () {
        return (React.createElement("div", null, "Foo"));
    };
    return FooComponent;
}(React.Component));
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = FooComponent;

注意,对 __extends 的调用仍然存在。毕竟,使 React 组件工作是必需的。如果咱们使用 --noEmitHelpers 标志,那么咱们就需要提供所需的所帮助函数,因为TypeScript 假设它们在运行时可用。

但是,手动跟踪所有这些帮助函数非常麻烦。咱必须检查应用程序需要哪些包,然后以某种方式使它们在包中可用。一点都不好玩了。还好,TypeScript 团队提出了一个更好的解决方案。

--importHelpers 标志和 tslib

TypeScript 2.1 引入了一个新的 --importHelpers 标志,它使编译器从tslib(一个外部帮助库)导入帮助函数,而不是将它们内联到每个文件中。安装 tslib 方式如下:

npm install tslib --save


再次编译 Reac t组件,这次使用 --importHelpers 标志:

"use strict";
var tslib_1 = require("tslib");
var React = require("react");
var FooComponent = (function (_super) {
    tslib_1.__extends(FooComponent, _super);
    function FooComponent() {
        return _super.apply(this, arguments) || this;
    }
    FooComponent.prototype.render = function () {
        return (React.createElement("div", null, "Foo"));
    };
    return FooComponent;
}(React.Component));
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = FooComponent;

请注意第2行中的 require("tslib") 调用和第5行中的 tslib_1.__extends 调用。此文件中不再内嵌帮助函数,而是从 tslib 模块导入 __extends 函数。这样,每个帮助函数仅在程序中包含一次,完美。


往期阅读

原文:https://mariusschulz.com/blog/compiling-async-await-to-es3-es5-in-typescript

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:【TypeScript 进化史 — 5】将 async、await 编译到 ES3、ES5 (外部帮助库)

链接:https://www.javascriptc.com/3355.html

« 如何用 WebAssembly 将桌面游戏编辑器移植到浏览器上
前端代码是怎样智能生成的——智能插件篇»
Flutter 中文教程资源

相关推荐

QR code