[译]TypeScript 2.0 Beta发布

今天我们非常兴奋地推出了TypeScript 2.0 beta版。如果你还不熟悉TypeScript,现在你就可以去我们的网站上学习。

获取beta版,你可以下载TypeScript 2.0 Beta for Visual Studio 2015 (此更新需要VS 2015 Update 3),或者运行

npm install -g typescript@beta

这个版本包含大量新特性,如新的方式获取.d.ts文件,但是这里的一些特性仅仅是对其了解。

非空类型

nullundefined是JavaScript bugs中最常见的两个来源。在TypeScript 2.0 之前,nullundefined存在每一类型域中。这意味着如果你有一个带有string类型参数的函数,你不能肯定是单一类型的,实际上有可能是string类型,也有可能是null类型。

在TypeScript 2.0,新的--strictNullChecks编译参数改变这一不确定性。string类型就是string类型,number类型就是number类型。

let foo: string = null; // 错误!

如果你为空呢?我们已经带来两个新的类型:nullundefined。正如你所料,null仅仅是null,undefined仅仅是undefined。对它们自己而言作用不大,但是你可以在联合类型中使用它们去定义是否可以包含null/undefined

let foo: string | null = null; // 正确!

由于作为开发者的你可能经常比编辑器的类型系统懂得更多,因此我们还推出了一个后缀操作符!,这个操作符可以从任何一个表达式从排除nullundefined

declare let strs: string[] | undefined;

// 错误! 'strs' 也许是undefined.
let upperCased = strs.map(s => s.toUpperCase());

// 'strs!' 意味着我们确定它不是'undefined', 因此我们可以调用'map'.
let lowerCased = strs!.map(s => s.toLowerCase());

基于控制流的类型分析

TypeScript对处理可空类型的支持成为可能,要归功于在整个程序中跟踪类型的变化。在2.0中,我们已经开始使用控制流分析,以便更好地了解在指定的位置是什么类型。例如,考虑下面这个函数。

/**
 * @param recipients An array of recipients, or a comma-separated list of recipients.
 * @param body Primary content of the message.
 */
function sendMessage(recipients: string | string[], body: string) {
 if (typeof recipients === "string") {
 recipients = recipients.split(",");
 }

// TypeScript 知道这里的'recipients'是'string[]'类型.
 recipients = recipients.filter(isValidAddress);
 for (let r of recipients) {
 // ...
 }
}

注意,在if块内赋值后,TypeScript知道它已经被处理成为了一个string类型的数组。这类事情可以在早期发现问题并节省你在调试上花费的时间。

let bestItem: Item;
for (let item of items) {
 if (item.id === 42) bestItem = item;
}

// 错误! 如果'items'是空的,'bestItem' 可能还没有被初始化。
let itemName = bestItem.name;

我们非常感谢Ivo Gabe de Wolff,他参与这一特性的实施工作,这也开始了他的论文项目并成长为TypeScript本身的一部分。

简易模块声明

有时候你仅仅想要告诉TypeScript有一个模块存在,并且你可能不关心它具体的内容是什么样子的。以前你要像下面这样写:

declare module "foo" {
 var x: any;
 export = x;
}

但是这样太麻烦的,因此我们使声明模块变得更加简易并且摆脱了样板。在TypeScript 2.0 中你可以这么写

declare module "foo";
declare module "bar";

当你准备好最终要写一个包含具体内容的模块时,你可以返回这些声明并定义你需要的结构。

如果你依靠一个有很多模块的软件包呢?为每一个模块书写一遍可能是痛苦的,但是TypeScript 2.0 通过使用通配符*使之变得容易。

declare module "foo/*";

现在您可以导入任何以foo/开头的路径,TypeScript会认为它存在。
你可以利用这一点,如果你的模块加载器也知道如何导入基于一个特定的模式。例如:

declare module "*!text" {
 const content: string;
 export = content;
}

现在每当你导入一个结尾带有!text的路径时,TypeScript就会知道导入的是string类型。

import text = require("./hello.txt!text");
text.toLowerCase();

关于模块这一部分,博客描述的太简单了,很多人可能不能明白,建议看看GitHub上的这部分内容

下一步

你可能想知道的一个特性是在ES3和ES5中支持async/await,最初,这是定于2.0版本。然而,合理的实现async/await,我们需要重写TypeScript的emitter,这会引起一系列的变化。这样做的同时还要保持TypeScript快速更新,就需要大量的工作和对细节的关注。虽然我们对于今天的实现充满了信心,但是信心不是彻底的测试,并且需要更多的时间用于async/await的稳定。你可以在TypeScript 2.1 中期待它的出现,如果你想要跟踪进度,在GitHub上的Pull request当前是公开的

TypeScript 2.0 还充满了许多有用的新功能,随着时间的推移我们将推出更多细节。如果你想听到更多的新特性,你可以看一下我们的wiki。在未来的几周内,一个更加稳定的候选版本将发布,随后最终的正式版将会发布。

我们很乐意听听你的任何反馈,无论是在下面的评论或GitHub上。黑客快乐!

Async/Await?

我们听到了你对async/await在TypeScript中感到兴奋的反馈。Async/await允许开发者去写异步的代码流就像写同步代码那样,不再需要注册事件处理程序或编写回调函数。在c#中你可能见过类似的模式。TypeScript的async/await使用了Promises,就像c#的async/await使用了Tasks。Promises是表示正在进行的异步操作的对象,是ECMAScript 6(ES6)的内置特性。TypeScript的async/await作为了ES2016(又称ES7)提出的实现标准。

我们非常高兴宣布你今天已经可以在Node.js v4及更高版本上使用async/await了!在这篇文章中,我们将向您展示如何使用async/await并且告诉你关于async/await的最新进展。

async/await是怎么工作的

JavaScript是单线程顺序运行的:一旦你的函数开始运行,在它完成之前不能中断。对于多任务,Async/await正是开发者所期望和想要的。然而,当一个异步任务(如调用一个Web服务)运行,在你等待这个任务返回时允许JavaScript的其余部分继续运行是非常高效的。Async/await允许你像调用同步方法的方式去调用异步方法,并且不会阻塞异步操作完成。

例如下面的代码,main等待异步函数ping的结果。因为main需要等待,因此它声明为一个异步函数。ping函数等待循环中的delay函数,因此它也声明为一个异步函数。delay函数调用setTimeout在一段时间后返回一个Promise对象。当setTimeout中的promise对象返回时,你将在看到控制台看到字符串‘ping’。

async function main() {
 await ping();
}

async function ping() {
 for (var i = 0; i < 10; i++) {
 await delay(300);
 console.log("ping");
 }
}

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

main();

TypeScript使用ES6 生成器去实现异步调用函数返回时重新进入函数能力。当一个函数等待执行时,生成器使用yield关键字去告诉JavaScript运行时当前执行操作被暂停(或者说冻结)。当函数执行完后,javascript运行时将当前执行操作激活(或者说解冻)并接着继续向下运行代码。对于上面的示例,ping函数被Typescript编译器生成了下面ES6版本的Javascript代码。

function ping() {
 return __awaiter(this, void 0, Promise, function* () {
 for (var i = 0; i < 10; i++) {
 yield delay(300);
 console.log("ping");
 }
 });
}

__awaiter函数包裹了一个函数块,其中包括yield语句,和一个作为生成器执行函数的Promise。

在Node.js尝试一下

开始使用Nightly版本的TypeScript(也就是最新的TypeScript版本,写这篇文章时Typescript最新版本为1.7),TypeScript 1.7版本中ES6的targets编译已经支持async/await。你可以使用npm install typescript@next命令安装最新的TypeScript版本,并且在Node.js v4版本及以上并且支持ES6生成器的环境中尝试。配置你的tsconfig.json文件如下这个样子:

"compilerOptions": {
 "target": "ES6",
 "module": "commonjs"
}

编译后的JavaScript就可以在Node.js环境中运行了:

在Node.js运行后的结果截图

如果你当前使用的Node.js是v4或者更高版本,并且今天要尝试一下async/await。我们已经使用GitHub的API创建了一个异步检索repo的pull requests历史的复杂示例。你可以在TypeScriptSamples repo找到源代码下载下来在Node.js环境中运行它。我们很想听听您的意见,如果你发现问题,请告诉我们。还有一件事情要注意:Node.js还没有支持ES6的模块,因此当你编辑TypeScript时选择CommonJs方式作为模块输出,如上述在tsconfig.json所示。

下一步

我们知道很多TypeScript的开发者想要在浏览器和Node.js中使用async/await,也明白无论是从开发者的工作流程角度来看还是由于性能的影响导致额外的编译开销,使用基于额外转译层的临时解决方案不是最佳的。针对众多的浏览器,我们需要使用状态机重写ES6的生成器使之成为可执行的ES5 JavaScript代码。虽然这是需要跨编译器重大变化的一大挑战,但是我们正在努力为之工作。敬请关注;我们将让您及时了解我们的进展!

  • 本文章翻译自What about Async/Await?
  • 本人英文水平有限,翻译不正确不通顺的地方,敬请指出。

TypeScript 1.7 发布,默认支持 ES6 async/await 特性

新版本的TypeScript已推出期待已久的ECMAScript 6异步功能。同时未来计划将该功能支持到ES3和ES5。TypeScript 1.7还包括多态this类型和一些重大的变化。

默认支持ECMAScript 6 (ES6)的async/await功能的微软JavaScript扩展语言TypeScript的博客最新连载已经发布。这意味着ES6 Generator支持的目标上比如Node4及以上版本现在可以去调用异步方法不受阻塞的去完成异步操作。

下面来自Github的示例已经提供了参考:

"use strict";

// printDelayed is a 'Promise<void>'
async function printDelayed(elements: string[]) {
 for (const element of elements) {
 await delay(200);
 console.log(element);
 }
}

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

printDelayed(["Hello", "beautiful", "asynchronous", "world"]).then(() => {
 console.log();
 console.log("Printed every element!");
});

看TypeScript的更新文档,TypeScript的团队计划在TypeScript2.0版中使async/await支持ES3和ES5。ES6也被添加到--module标记的可供选择列表中,并且允许你指定ES6时指定的模块输出。

{
 "compilerOptions": {
 "target": "es6",
 "module": "es6",
 "sourceMap": true
 }
}

另外一个新特性介绍了在多态中引入了this类型以便于创建流式风格的APIs。this类型在描述类库中多类型交集中也是有效的就像Ember.js中使用mixin-style表达式去描述继承。

最后但并非最不重要,微软的团队已经知道了ECMAScript委员会最近将求幂运算符提议移动到了第三阶段并且在TypeScript1.7版本中支持操作符****=。该操作符在ES3和ES5中将被转化为Math.pow输出。

重大改变

关于重大改变,在一个类中,值this的类型将会被推断为this类型。推荐开发者可以为this添加一个类型注释,比如下面的示例:

示例:

class Fighter {
 /** @returns the winner of the fight. */
 fight(opponent: Fighter) {
 let theVeryBest = this;
 if (Math.rand() < 0.5) {
 theVeryBest = opponent; // error
 }
 return theVeryBest
 }
}

建议:

添加类型注解:

class Fighter {
 /** @returns the winner of the fight. */
 fight(opponent: Fighter) {
 let theVeryBest: Fighter = this;
 if (Math.rand() < 0.5) {
 theVeryBest = opponent; // no error
 }
 return theVeryBest
 }
}

关键字abstract, public, protectedprivate在ES3中是未来保留字并且受到自动分号插入 (ASI)的影响。以前TypeScript在这些关键词自成一行时绝不允许插入分号,现在这个问题修复了,在下面的示例中abstract class D已不再正确继承C, 并且相反的是定义了一个具体的方法m和添加了一个abstract属性。

示例:

abstract class C {
 abstract m(): number;
}
abstract class D extends C {
 abstract
 m(): number;
}

建议:
为了避免出现问题,在定义类成员的关键字后删除换行符,一般来说能避免受到ASI的影响。

对于TypeScript更加详细的描述,请访问他们在Github上的代码仓库。