[History of TypeScript Evolution--5] Compile async/await to ES3/ES5 (External Help Library)

Posted by kenslate on Mon, 25 Nov 2019 02:14:54 +0100

Author: Marius Schulz
Translator: Front-end wit
Source: Marius Schulz

Ali Yun has been doing activities recently, with a discount of as low as 2 percent. I really think it's a good deal. You can click on this item or link to participate.:
https://promotion.aliyun.com/...

Tencent Cloud is currently doing activities with 100 cloud products as low as 1% discount. You can click on this item or link to participate.

To ensure readability, this paper uses free translation instead of literal translation.

Released since November 2015 Version 1.7 TypeScript has since supported the async/await keyword.The compiler uses yield to convert an asynchronous function to a generator function.This means we can't target ES3 or ES5 because the generator was only introduced in ES6.

TypeScript 2.1 now supports compiling asynchronous functions to ES3 and ES5.As with the rest of the generated code, they run in all JS environments.(This even includes IE6, which of course is not recommended to go compatible with browsers as old as this.)

Using asynchronous functions

The following is a simple function that parses a Promise after a given number of milliseconds.Use the built-in setTimeout function to call the resolve callback after ms milliseconds:

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

The delay function returns a promise that can be called using await to wait for, as follows:

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

async function asyncAwait () {
  console.log('Start execution...');

  await delay(1000);

  console.log('1 Seconds later')

  await delay(1000);

  console.log('Execution completed in 2 seconds');
}

Now, if you call the asyncAwait function, you see three messages in the console, one after the other, with a pause between each message.

asyncAwait();
// Start execution...
// After 1 second
// Execution completed in 2 seconds

Now, take a look at the JS code generated by TypeScript for ES2017, ES2016/ES2015, and ES5/ES3.

Compile async/await to ES2017

Asynchronous functions are a JavaScript language feature standardized in ES2017.

Therefore, for ES2017, the TypeScript compiler does not need to override async/await to some other construct because both asynchronous functions are natively supported.The generated JS code is the same as the TypeScript code except that all type comments and blank lines have been removed:

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

async function asyncAwait () {
  console.log('Start execution...');

  await delay(1000);

  console.log('1 Seconds later')

  await delay(1000);

  console.log('Execution completed in 2 seconds');
}

There's nothing to say here. It's our own code, just no type notes.

Compile async/await to ES2015/ES2016

For ES2015, the TypeScript compiler overrides async/await using the generator function and the yield keyword.It also generates the u awaiter help method as a runner of an asynchronous function.The results of the asyncAwait function above are compiled into JS code as follows:

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('Start execution...');
        yield delay(1000);
        console.log('1 Seconds later');
        yield delay(1000);
        console.log('Execution completed in 2 seconds');
    });
}

Geneed auxiliary code is negligible, but not bad.If you want to use async/await in Node 6.x or 7.x applications, set target to ES2015 or ES2016 in the configuration you need.

Note that the only feature of ES2016 standardization is the exponentiation operator and the Array.prototype.includes method, which are not used here.Therefore, the JS code generated for ES2016 is the same as the code generated for ES2015.

Compile async/await to ES3/ES5

The interesting thing is that with TypeScript 2.1, the compiler can downgrade an asynchronous function to ES3 or ES5. Here's an example of what we've done before:

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('Start execution...');
                    return [4 /*yield*/, delay(1000)];
                case 1:
                    _a.sent();
                    console.log('1 Seconds later');
                    return [4 /*yield*/, delay(1000)];
                case 2:
                    _a.sent();
                    console.log('Execution completed in 2 seconds');
                    return [2 /*return*/];
            }
        });
    });
}
asyncAwait();

There is a lot of help code in it.In addition to the u awaiter function you've seen earlier, the compiler injects another help function called generator, which uses a state machine to simulate a generator function that can be paused and restored.

Note that in order for your code to run successfully in an ES3 or ES5 environment, Promise polyfill is required, as Promise was introduced only in ES2015.Also, you must let TypeScript know that it can find the Promise function at run time.This was described in the previous chapter, TypeScript 2.0: Built-in type declarations.

With it, async/await runs in all JS engines.

Next, let's see how to avoid writing these auxiliary functions over and over again for each TypeScript file in the compilation.

External Help Library in TypeScript

In some cases, the TypeScript compiler injects help functions into the generated output code called at run time.Each of these help functions simulates the semantics of specific language features that are not supported by the compilation target (ES3, ES5, ES2015).

Currently, there are the following help functions in TypeScript

  • _u extends for inheritance
  • _u assign is used to extend object properties
  • _rest is used to represent the remaining properties of an object
  • There are also decorators u decorate, u param, u metadata
  • _u awaiter and u generator for async/await

A typical use case for the ES6 class with extends is the React component shown below:

import * as React from "react";

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

If the configured target is ES5, the TypeScript compiler will generate (emit) the following JS code, which does not support either class or 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;

Although this method works well for such a simple example, it has one big disadvantage: injecting u extends help function code into each compiled file of a class that uses an extends statement.That is, help functions are triggered for each class-based React component in the application.

For a medium-sized application with dozens or hundreds of React components, there is a lot of duplicate code for the u extends function.A lot of duplicate code also increases the package size, so download time increases.

This problem will only exist for other helpful functions, such as how to downgrade async/await to u awaiter and u generator help functions in ES3/ES5 at the beginning.Note that they are injected into each file that uses the async/await keyword:

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]); }; }
    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 Flag

In version 1.5, TypeScript was added--the noEmitHelpers flag.When this compiler option is specified, TypeScript does not generate any help functions after compilation.In this way, the bundle size will be much smaller.

Here is the previous React component, compiled with the--noEmitHelpers flag:

"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;

Note that the call to u extends still exists.After all, making React components work is necessary.If we use the--noEmitHelpers flag, then we need to provide the help functions we need, because TypeScript assumes they are available at runtime.

However, tracking all these help functions manually can be cumbersome.We have to check which packages the application needs and make them available in the package in some way.It's not fun at all.Fortunately, the TypeScript team has come up with a better solution.

--importHelpers flag and tslib

TypeScript 2.1 introduces a new--importHelpers flag, which allows the compiler to import help functions from tslib, an external help library, instead of inlining them into each file.Install tslib as follows:

npm install tslib --save

Compile the Reac t component again, this time using the--importHelpers flag:

"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;

Notice the require("tslib") call in line 2 and the tslib_1. u extends call in line 5.Instead of embedding help functions in this file, import the u extends function from the tslib module.In this way, each help function is included only once in the program, which is perfect.

Possible bugs in editing can not be known in real time. In order to solve these bugs afterwards, a lot of time has been spent debugging the log. By the way, a useful bug monitoring tool is recommended. Fundebug.

Original:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...

Communication

The dry goods series articles are summarized below, feel good to order Star, welcome to join the group to learn from each other.

https://github.com/qq449245884/xiaozhi

Due to space limitations, today's share is here only.If you want to know more, you can sweep the QR code at the bottom of each article, and then follow our WeChat public number for more information and valuable content.

Topics: Javascript React TypeScript REST