做者:Marius Schulzhtml
译者:前端小智前端
来源:Marius Schulzreact
阿里云最近在作活动,低至2折,真心以为很划算了,能够点击本条内容或者连接进行参与: promotion.aliyun.com/ntms/yunpar…git
腾讯云最近在作活动,百款云产品低至 1 折,能够点击本条内容或者连接进行参与github
为了保证的可读性,本文采用意译而非直译。typescript
自2015年11 发布1.7版以来,TypeScript 已支持 async/await
关键字。编译器使用 yield
将异步函数转换为生成器函数。这意味着我们没法针对 ES3 或 ES5,由于生成器仅在 ES6 中引入的。npm
TypeScript 2.1 如今支持将异步函数编译为 ES3 和 ES5。与生成的其他代码同样,它们在全部 JS 环境中运行。(这甚至包括IE6,固然不建议在去兼容这么古老的浏览器了)promise
下面是一个简单的函数,它在给定的毫秒数以后解析一个 Promise
。使用内置的 setTimeout
函数在 ms
毫秒事后调用 resolve
回调:浏览器
function delay(ms: number) {
return new Promise<void>(function(resolve) {
setTimeout(resolve, ms)
})
}
复制代码
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 代码。
异步函数是一种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 秒后执行完成');
}
复制代码
这里没什么可说的,这是我们本身编写的代码,只是没有类型注释。
针对 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
,须要的配置中设置target
为 ES2015
或 ES2016
。
请注意,ES2016 标准化的唯一特性是求幂运算符和 Array.prototype.includes
方法,这里两个方法都不使用。所以,针对 ES2016 生成的 JS 代码与针对 ES2015 生成的代码相同。
有趣的地方是,使用 TypeScript 2.1,可让编译器将异步函数降级到 ES3 或 ES5,下面是我们以前的例子:
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 编译器会将帮助函数注入到在运行时调用的生成输出代码中。每一个这样的帮助函数都模拟编译目标(target) (ES三、ES五、ES2015) 自己不支持的特定语言特性的语义。
目前,TypeScript 中有如下帮助函数
__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]); }; }
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 };
}
};
复制代码
在 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 团队提出了一个更好的解决方案。
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
函数。这样,每一个帮助函数仅在程序中包含一次,完美。
编辑中可能存在的bug无法实时知道,过后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
原文: mariusschulz.com/blog/extern… mariusschulz.com/blog/compil…
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。