一、什么是 Deno

Deno 是一个安全的 JavaScript 和 TypeScript 运行时。 我猜您可能对试验一种新工具感到兴奋。 你使用过 JavaScript 或 TypeScript,至少听说过 Node.js。 Deno 会觉得它对你来说有足够的新鲜感,同时,有些东西对在生态系统中工作的人来说听起来很熟悉。

在我们开始动手之前,我们先了解一下 Deno 是如何创建的以及它的动机。 这样做会帮助我们更好地学习和理解它。

在本书中,我们将着重于实际例子。 我们将编写代码,然后合理化并解释我们所做的潜在决定。 如果您有 Node.js 的背景,那么其中一些概念可能对您来说很熟悉。 我们还将解释 Deno,并将其与它的祖先 Node.js 进行比较。

一旦基础就绪,我们将深入研究 Deno,并通过构建小型实用程序和实际应用探索其运行时特性。

没有 Node,就没有 Deno。 为了更好地理解后者,我们不能忽视它 10 多年前的祖先,这是我们将在本章中看到的。 我们将解释它在 2009 年创建的原因,以及在使用十年后发现的痛点。

之后,我们将介绍 Deno 和它的根本区别以及它提出要解决的挑战。 我们将看一看它的架构、运行时的一些原则和影响,以及它的亮点所在。

在了解了 Deno 是如何出现的之后,我们将探索它的生态系统,它的标准库,以及一些在 Deno 起作用的用例。

一旦你读了这一章,你会意识到什么是 Deno 和什么不是,为什么它不是 Node.js 的下一个版本,以及当你考虑 Deno 为你的下一个项目时应该考虑什么。

在本章中,我们将涵盖以下主题:

  • 一些历史
  • 为什么 Deno ?
  • 支持 Deno 的架构和技术
  • 把握 Deno 的局限性
  • 探索 Deno 的用例

让我们开始吧!

A little history

Deno 的第一个稳定版本 v1.0.0 于 2020 年 5 月 13 日发布。

Ryan Dahl - Node.js 的创建者-第一次提到它是在他著名的演讲中,关于 Node.js 我后悔的 10 件事(https://youtu.be/M3BM9TB-8yA)。 除了展示了 Deno 的第一个 alpha 版本之外,这是一场值得观看的演讲,可以作为软件如何老化的一堂课。 它很好地反映了决策是如何演变的,即使这些决策是由开源社区中一些最聪明的人做出的,以及它们如何可能最终出现在与最初计划不同的地方。

在 2020 年 5 月推出后,由于其历史背景、核心团队以及对 JavaScript 社区的吸引力,Deno 一直备受关注。 这可能是你听说过的一种方式,可能是通过博客、推特或会议发言。

这种热情对它的运行产生了积极的影响,许多人想要贡献和使用它。 由于其 Discord 频道(https://discord.gg/deno)和 Deno 仓库(https://github.com/denoland)上的拉请求,社区正在增长。 目前,它正在以一个月一个小版本的速度发展,大量的 bug 修复和改进正在进行中。 路线图显示了对未来的展望,未来并不比现在更令人兴奋。 有了明确的道路和原则,德诺有一切需要变得更重要的一天。

让我们倒带一点,回到 2009 年 Node.js 的创建。

当时,Ryan 开始质疑大多数后端语言和框架如何处理 I/O(输入/输出)。 大多数工具将 I/O 视为同步操作,阻塞进程直到它完成,然后才继续执行代码。

从根本上说,Ryan 质疑的正是这种同步阻塞操作。

处理 I/O

当您编写每秒必须处理数千个请求的服务器时,资源消耗和速度是两个重要因素。

对于这样的资源关键型项目,重要的是基本工具(原语)要有一个体系结构来解决这个问题。 当扩大规模的时间到来时,你在一开始所做的基本决定会对扩大规模有所帮助。

Web 服务器就是其中之一。 网络是当今世界的一个重要平台。 互联网从未停止增长,每天都有更多的设备和新工具接入互联网,使更多的人可以使用互联网。 对于世界各地的人们来说,网络是共同的、民主化的、去中心化的场所。 考虑到这一点,这些应用和网站背后的服务器需要处理巨大的负载。 Twitter、Facebook 和 Reddit 等网络应用每分钟处理数千个请求。 因此,规模至关重要。

为了开启关于性能和资源效率的对话,让我们看看下面的图表,它比较了两个最常用的开源 web 服务器:Apache 和 Nginx:

Figure 1.1 – Requests per second versus concurrent connections – Nginx versus Apache

图 1.1 -每秒请求数与并发连接数- Nginx 与 Apache

乍一看,这告诉我们 Nginx 几乎每次都名列前茅。 我们还可以理解,随着并发连接数量的增加,Apache 每秒的请求数量会减少。 相比之下,Nginx 保持了相当稳定的每秒请求数,尽管随着连接数的增长,每秒请求数也出现了预期的下降。 当达到 1000 个并发连接时,Nginx 的请求数量接近 Apache 每秒请求数量的两倍。

让我们看一下 RAM 内存消耗的比较:

Figure 1.2 – Memory consumption versus concurrent connections – Nginx versus Apache

图 1.2 -内存消耗与并发连接- Nginx 与 Apache

Apache 的内存消耗随着并发连接的数量线性增长,而 Nginx 的内存占用是恒定的。

你可能已经想知道为什么会发生这种情况。

这是因为 Apache 和 Nginx 有非常不同的方法来处理并发连接。 Apache 会为每个请求生成一个新线程,而 Nginx 则使用一个事件循环。

thread per-request架构中,每当有新请求传入时,它都会创建一个线程。 该线程负责处理请求,直到它完成为止。 如果在前一个请求还在处理时又来了一个请求,就会创建一个新线程。

最重要的是,在线程环境中处理网络不是一件特别容易的事情。 您可能会导致文件和资源锁定、线程通信问题以及死锁等常见问题。 给开发人员增加困难的是,使用线程并不是免费的,因为线程本身就有资源开销。

相反,在事件循环架构中,所有事情都在单个线程上发生。 这个决定极大地简化了开发人员的工作。 你不必考虑前面提到的因素,这意味着你有更多的时间来处理用户的问题。

通过使用这种模式,web 服务器只向事件循环发送事件。 它是一个异步队列,当有可用资源时执行操作,当这些操作完成时异步返回到代码。 要实现此功能,所有操作都需要是非阻塞的,这意味着它们不应该等待完成,而只是发送一个事件,然后等待稍后的响应。

阻塞和非阻塞

例如,读取一个文件。 在阻塞环境中,您将读取文件,并让进程等待它完成,直到您执行下一行代码。 当操作系统读取文件内容时,程序处于空闲状态,浪费了宝贵的 CPU 周期:

const result = readFile('./README.md');
// Use result

程序将等待文件被读取,只有这样它才会继续执行代码。

使用事件循环的相同操作将触发“读取文件”事件并执行其他任务(例如,处理其他请求)。 当文件读取操作完成时,事件循环将使用结果调用回调函数。 这一次,运行时使用 CPU 周期来处理其他请求,而操作系统检索文件的内容,更好地利用资源:

const result = readFileAsync('./README.md', function(result) {
  // Use result
});

在本例中,任务获得分配给它的回调。 当任务完成时(这可能需要几秒或几毫秒),它将返回带有结果的函数。 当调用这个函数时,函数内部的代码会线性运行。

为什么不更经常地使用事件循环?

既然我们已经了解了事件循环的好处,那么这个问题就很有道理了。 尽管在 Python 和 Ruby 中有一些实现,但事件循环没有被更多地使用的原因之一是,它们要求所有的基础设施和代码是非阻塞的。 非阻塞意味着准备好不以同步方式执行代码。 它意味着触发事件并在稍后的某个时间点处理结果。

最重要的是,许多常用的语言和库不提供异步 api。 回调在许多语言中都不存在,匿名函数在 c 等编程语言中也不存在。当今软件的关键部分,如libmysqlclient,不支持异步操作,即使它的部分内部可能使用异步任务执行。 异步 DNS 解析是在许多系统中也不是标准的另一个例子。 作为另一个例子,您可以以操作系统的手册页为例。 他们中的大多数甚至没有提供给我们一种方法,以了解一个特定的功能是否做 I/O。 这些都证明了异步 I/O 的能力在当今的许多基本软件中并不存在。

即使是提供这些特性的现有工具,也需要开发人员对异步 I/O 模式有深刻的理解,才能使用事件循环。 在克服技术限制(如libmysqlclient示例中所示)的同时,将这些现有解决方案连接起来以使其发挥作用是一项困难的工作。

JavaScript 的拯救

JavaScript 是 Brendan Eich 在 1995 年为 Netscape 工作时创建的一种语言。 它最初只在浏览器中运行,允许开发者在网页中添加交互功能。 它是由一些元素组成的,这些元素对事件循环来说是完美的:

  • 它有匿名函数和闭包。
  • 它一次只执行一个回调。
  • I/O 是通过回调在 DOM 上完成的(例如,addEventListener)。

结合语言的这三个基本方面,使得事件循环对于浏览器中使用 JavaScript 的人来说很自然。

该语言的特性最终将其开发人员推向了事件驱动编程。

Node.js 进入场景

在所有这些关于 I/O 和如何处理它的想法和问题之后,Ryan Dahl 在 2009 年提出了 Node.js 的。 它是一个 JavaScript 运行时,基于谷歌的 V8——一个将 JavaScript 带到服务器的 JavaScript 引擎。

Node.js 是异步的单线程设计。 它的核心是一个事件循环,并以一种可扩展的方式来开发处理数千个并发请求的后端应用。

事件循环为我们提供了一种处理并发性的干净方法,这是 Node.js 与 PHP 或 Ruby 等工具的不同之处,PHP 或 Ruby 使用每请求一个线程的模型。 这种单线程环境赋予 Node.js 用户无需关心线程安全问题的简单性。 它非常成功地从用户那里抽象出了事件循环和同步工具的所有问题,几乎不需要了解事件循环本身。 Node.js 通过利用回调来实现这一点,最近还使用了承诺。

Node.js 将自己定位为一种为用户编程他们的应用提供低级、纯事件、非阻塞基础设施的方式。

node . js 的崛起

告诉公司和开发人员,他们可以利用他们的 JavaScript 知识来编写服务器,这很快导致了 Node.js 的流行。

自从该语言发布以来,并开始被各种规模的公司用于生产后,它并没有花太多时间来快速发展。

2011 年,优步和领英创建仅两年,就已经在服务器上运行 JavaScript 了。 2012 年,Ryan Dahl 辞去了 Node.js 社区的日常运营工作,投身于研究和其他项目。

估计,在 2017 年,有超过 880 万个 Node.js 实例在运行(来源:https://blog.risingstack.com/history-of-node-js/)。 今天,节点包管理器(npm)下载的包超过 1030 亿,发布的包约 1467527 个。

Node.js 是一个很棒的平台,这是毫无疑问的。 几乎所有使用过它的人都体验过它的许多优点。 人气和社区在这方面发挥了重要作用。 让许多拥有不同经验水平和背景的人参与一项技术的开发只能推动它向前发展。 这就是 Node.js 所发生的事情,现在仍然在发生。

Node.js 使开发人员能够使用 JavaScript 来处理许多不同的用例,这在以前是不可能的。 这包括机器人、加密货币、代码绑定器、api 等等。 它是一个稳定的环境,让开发人员感到高效和快速。 它将继续它的工作,在未来许多年支持不同规模的公司和企业。

但是你买了这本书,所以你一定相信 Deno 有一些值得探索的东西,我可以保证它是。

你可能会想,既然以前的解决方案非常令人满意,为什么还要提出新的解决方案呢? 这就是我们接下来要发现的。

为什么是 Deno?

自从 Node.js 创建以来,很多事情都发生了变化。 十几年过去了; JavaScript 和软件基础设施社区都在不断发展。 像 Rust 和 golang 这样的语言诞生了,并且是软件社区中非常重要的发展。 这些语言使得生成本机代码变得更加容易,同时为开发人员提供了严格和可靠的工作环境。

然而,这种严格是以牺牲生产力为代价的。 并不是说开发人员在编写这些语言时感觉没有生产力,因为他们确实有生产力,但你可以很容易地说,生产力是动态语言明显发挥作用的一个主题。

在编写脚本和原型时,开发动态语言的简单性和速度使它们成为非常强大的竞争者。 说到动态语言,首先想到的就是 JavaScript。

JavaScript 是最常用的动态语言,可以在每一个带有 web 浏览器的设备上运行。 由于它的大量使用和庞大的社区,很多人都在努力优化它。 ECMA 国际等组织的成立确保了该语言的稳定和谨慎发展。

正如我们在上一节中看到的,Node.js 在将 JavaScript 引入服务器方面扮演了一个非常成功的角色,为大量不同的用例打开了大门。 它目前被用于许多不同的任务,包括 web 开发工具、创建 web 服务器和编写脚本等。 在它创建的时候,为了支持这样的用例,Node.js 必须为 JavaScript 发明以前不存在的概念。 后来,标准组织对这些概念进行了讨论,并以不同的方式添加到语言中,使得 Node.js 的某些部分与其母语 ECMAScript 不兼容。 十年过去了,ECMAScript 和围绕它的生态系统都在不断发展。

CommonJS模块不再是标准; JavaScript 现在有 ES 模块。 TypedArrays现在已经有了,最后,JavaScript 可以直接处理二进制数据。 承诺和异步/等待是异步 ronous 操作的首选方法。

这些特性可以在 Node.js 上使用,但它们必须与 2009 年创建的仍需要维护的非标准特性共存。 这些特性,以及 Node.js 拥有的大量用户,使系统的发展变得困难和缓慢。

为了解决其中一些问题,并跟上 JavaScript 语言的发展,创建了许多社区项目。 这些项目使我们能够使用该语言的最新特性,但也为许多 Node.js 项目添加了构建系统等内容,使它们变得非常复杂。 引用 Dahl 的话,它“剥夺了动态语言脚本的乐趣

*超过 10 年的大量使用也清楚地表明,运行时的一些基本构造需要改进。 缺乏安全沙箱是主要问题之一。 在 Node.js 被创建的时候,JavaScript 可以通过在 V8 中创建绑定来访问“外部世界”——V8 是它背后的 JavaScript 引擎。 尽管这些绑定启用了 I/O 特性,比如从文件系统读取数据访问网络,但它们也破坏了 JavaScript 沙箱的用途。 这个决定使得开发者很难控制 Node.js 脚本的访问权限。 例如,在当前状态下,没有什么能阻止 Node.js 脚本中的第三方包读取用户可以访问的所有文件,以及执行其他恶意操作。

十年后,Ryan Dahl 和 Deno 背后的团队失去了一个有趣而高效的脚本环境,可以用于广泛的任务。 开发团队还认为 JavaScript 的前景已经发生了很大的变化,值得进行简化,因此他们决定创建 Deno。

Deno

“Deno 是一个简单、现代、安全的 JavaScript 和 TypeScript 运行时,使用 V8,内置在 Rust 中。” -https://deno.land/

Deno 的名字是由其祖先的名字 no-de, de-no 的音节颠倒而成的。 从它的祖先吸取了很多教训,Deno 呈现出以下是它的主要特征:

  • 缺省安全
  • 一流的打印稿的支持
  • 单个可执行文件
  • 提供编写应用的基本工具
  • 完成并审核标准库
  • 兼容 ECMAScript 和浏览器环境

Deno 在默认情况下是安全的,它是这样设计的。 它最终利用了 V8 沙箱,并提供了一个严格的权限模型,使开发人员能够很好地控制代码的访问权限。

TypeScript 也是一级支持的,这意味着开发者可以选择使用 TypeScript 而不需要任何额外的配置。 所有的 Deno api 都是用 TypeScript 编写的,因此有正确和精确的类型和文档。 对于标准库也是如此。

Deno 提供了一个带有编写应用所需的所有基本工具的可执行文件; 它将永远如此。 团队努力使可执行文件保持较小(~15 MB),以便我们可以在各种情况和环境中使用它,从简单的脚本到成熟的应用。

除了执行代码,Deno 二进制文件还提供了一组完整的开发人员实用程序,即 linter、formater 和测试运行器。

Golang 精心打磨的标准库启发了 Deno 的标准库。 它故意比 Node.js 更大、更完整。 做出这个决定是为了解决一些 Node.js 项目中经常出现的巨大依赖树。 Deno 的核心团队相信,通过提供一个稳定和完整的标准库,它可以帮助解决这个问题。 通过消除创建第三方包来处理平台默认提供的通用用例的需要,它旨在减少使用第三方包负载的需要。

为了保持与 ES6 和浏览器的兼容性,Deno 努力模仿浏览器 api。 诸如执行 HTTP 请求、处理 url 或编码文本等,都可以通过使用浏览器中使用的相同 api 来完成。 Deno 团队特意努力使这些 api 与浏览器保持同步。

Deno 的目标是提供三个世界中最好的,它提供了 JavaScript 的原型能力和开发者体验,Typescript 提供的类型安全性和安全性,以及 Rust 的性能和简单性。

理想情况下,正如 Dahl 在他的一次演讲中提到的,代码在从原型到生产的过程中应该遵循以下流程:开发人员可以开始编写 JavaScript,然后迁移到 TypeScript,最后编写 Rust 代码。

在撰写本文时,是否只能运行 JavaScript 和 TypeScript。 Rust 只能通过一个(仍然不稳定的)插件 API 来使用,这个 API 可能在不久的将来会变得稳定。

命令行脚本的 web 浏览器

随着时间的推移,Node.js 模块系统演变成了现在过于复杂的,维护起来很痛苦。 它考虑了一些边缘情况,比如导入文件夹、搜索依赖项、导入相关文件、搜索 index.js、第三方包和读取package.json文件等等。

它还与npm紧密耦合,Node 包管理器最初是 Node.js 本身的一部分,但在 2014 年分离。

用达尔的话来说,拥有一个集中式的包管理器并不是很容易实现的。 数以百万计的应用依靠一个注册中心生存是一种负担。

Deno 通过使用 url 解决了这个问题。 它采用了一种非常类似于浏览器的方法,只需要一个文件的绝对 URL 来执行或导入代码。 这个绝对 URL 可以是本地的、远程的或基于 http 的,包括以下文件扩展名:

import { serve } from 'https://deno.land/std@0.83.0/http/server.ts'

前面的代码与您在浏览器中使用<script>标签编写的代码相同,如果您想要要求 ES 模块。

在安装和离线使用方面,Deno 通过使用本地缓存确保用户不必担心这些问题。 当程序运行时,它将安装所有必需的依赖项,从而无需安装步骤。 我们将在随后的第二章工具链中深入探讨。

现在,我们对 Deno 是什么和它解决的问题感到舒服,我们在良好的形状去超越表面。 通过了解幕后发生了什么,我们可以更好地理解 Deno 本身。

在下一节中,我们将探索支持 Deno 的技术以及它们之间的联系。

支持 Deno 的架构和技术

在架构方面,Deno 考虑了各种主题,比如安全性。 Deno 投入了大量的思想,以建立一种干净、高效的与底层操作系统通信的方式,而不会将细节泄露给 JavaScript 端。 为了实现这一点,Deno 使用消息传递从 V8 内部与 Deno 后端进行通信。 后端是用 Rust 编写的组件,它与事件循环以及操作系统进行交互。

Deno 之所以成为可能,得益于以下四项技术:

  • V8
  • 打印稿
  • 东京(事件循环)
  • 生锈

正是这四个部分的连接,使得我们能够在保证代码安全和沙箱化的同时,为开发人员提供良好的体验和开发速度。 如果你不熟悉这些技术,我将留下一个简短的定义:

V8是谷歌开发的 JavaScript 引擎。 它是用 c++编写的,可以在所有主要的操作系统上运行。 它也是 Chrome、Node.js 和其他浏览器背后的引擎。

TypeScript是微软开发的 JavaScript 的超集,它添加了可选的静态类型到语言中,转换为 JavaScript。

Tokio是 Rust 的一个异步运行时,为编写任何规模的网络应用提供实用程序。

Rust是一种服务器端语言,由 Mozilla 设计,专注于性能和安全。

使用 Rust(一种快速发展的语言)来编写 Deno 的核心,使得它比 Node.js 更容易被开发人员接受。 Node.js 的核心是用 c++编写的,而 c++并不是特别容易处理的。 由于存在许多缺陷和不太好的开发经验,c++暴露出自己是 Node.js 核心发展中的一个小障碍。

Deno_core作为锈箱(包)装运。 和拉斯特的联系不是巧合。 Rust 提供了许多功能,促进了与 JavaScript 的连接,并为 Deno 本身添加了功能。 Rust 中的异步操作通常使用 future,它可以很好地映射 JavaScript promise。 Rust 也是一种可嵌入的语言,它为 Deno 提供了直接的嵌入功能。 这使得 Rust 成为为WebAssembly创建编译器的第一批语言之一,使得 Deno 团队选择它作为核心。

来自 POSIX 系统的启发

POSIX 系统给了 Deno 很大的启发。 在他的一次谈话中,达尔甚至指出,Deno 处理它的一些任务“作为一个操作系统”

下表展示了来自 POSIX/Linux 系统的一些标准术语,以及它们如何映射到 Deno 概念:

您可能对 Linux 世界中的一些概念很熟悉。 让我们以进程为例。 它们表示可能使用一个或多个线程执行的正在运行的程序的实例。 Deno 使用 WebWorkers 在运行时内完成相同的工作。

在第二行,我们有系统调用。 如果你不熟悉它们,它们是程序向内核执行请求的方式。 在 Deno 中,这些请求不会直接发送到内核; 相反,它们从 Rust 核心转向底层操作系统,但它们的工作方式相似。 我们将有机会在接下来的架构图中看到这一点。

如果您熟悉 Linux/POSIX 系统,这些只是几个示例。

我们将在本书的其余部分解释和使用前面提到的大部分 Deno 概念。

建筑

德诺的核心最初写的是golang,但后来改为 Rust。 是为了摆脱golang,因为 golang 是一种垃圾收集语言。 它与 V8 的垃圾收集器的组合可能会导致未来的问题。

为了理解底层技术如何相互作用形成 Deno 核心,让我们看一下它的架构图:

Figure 1.3 – Deno architecture

图 1.3 - Deno 架构

Deno 使用消息传递与 Rust 后端通信。 作为一个关于特权隔离的决定,Deno 从不向 Rust 公开 JavaScript 对象句柄。 V8 内外的所有通信都使用Uint8Array实例。

对于事件循环,Deno 使用 Tokio,一个 Rust 线程池。 Tokio 负责处理 I/O 工作和回调 Rust 后端,使其能够异步处理所有操作。 Operations(ops)是在 Rust 和事件 loo 之间来回传递的消息的名称。

所有从 Deno 的代码发送到其核心(用 Rust 写)的异步消息返回promise给 Deno。 更准确地说,《Rust》中的异步操作通常返回Futures,Deno 将其映射到 JavaScript promise。 当这些Futures被解析时,JavaScriptPromises也被解析。

为了实现从 V8 到 Rust 后台的通信,Deno 使用了rusty_v8,一个由 Deno 团队创建的 Rust 板条箱,提供 V8 绑定到 Rust。

Deno 还在 V8 中包含了 TypeScript 编译器。 它使用 V8 快照来优化启动时间。 快照用于在特定的执行时间保存 JavaScript 堆,并在需要时恢复它。

自从它第一次被提出以来,Deno 就经历了一个迭代的进化过程。 如果你好奇它改变了多少,你可以看看一个最初的路线图在 2018 年编写的文档由瑞安哒 l (https://github.com/ry/deno/blob/a836c493f30323e7b40e988140ed2603f0e3d10f/Roadmap.md)。

现在,我们不仅知道 Deno 是什么,还知道幕后发生了什么。 这些知识将在将来运行和调试应用时帮助我们。 Deno 的创始人做出了许多技术和架构上的决定,将 Deno 带到今天的状态。 这些决策推动了运行时的前进,并确保了 Deno 在几种情况下表现出色,其中一些情况我们将在后面进行探讨。 然而,为了使它在某些用例中正常工作,必须做出一些权衡。 这些权衡导致了我们接下来将讨论的限制。

了解 Deno 的局限性

和所有事情一样,选择解决方案也是一个权衡取舍的问题。 那些最适合我们正在编写的项目和应用的就是我们最终使用的。 目前,Deno 有一些局限性; 有些是因为它的生命周期短,有些是因为设计决策。 与大多数解决方案一样,Deno 也不是万能的工具。 在接下来的几页中,我们将探索 Deno 当前的一些局限性及其背后的动机。

不如 Node.js 稳定

在目前的状态下,Deno 在稳定性方面无法与 Node.js 相比,原因很明显。 Node.js 有 10 多年的发展历程,而 Deno 刚刚接近第二个年。

尽管本书中介绍的大多数核心特性已经被认为是稳定的,并且得到了正确的版本,但是仍然有一些特性是可以改变的,并且处于不稳定的状态下。

Node.js 多年的经验确保了它是经过实战考验的,并且能在最多样化的环境中工作。 这是我们希望 Deno 能够做到的,但时间和采用是关键因素。

更好的 HTTP 延迟但更差的吞吐量

Deno 从一开始就保持着良好的表现。 然而,正如在基准测试页面(https://deno.land/benchmarks)上看到的,有些主题仍然不在 Node.js 的级别。

它的祖先利用了 HTTP 服务器上与 c++的直接绑定来放大这个性能分数。 由于 Deno 拒绝添加本地 HTTP 绑定和构建在本地 TCP 套接字之上,因此它仍然会受到性能损失。 这个决定是团队计划在优化 TCP 套接字通信之后解决的问题。

Deno HTTP 服务器每秒处理 25k 请求,最大延迟 1.3 毫秒,而 Node.js 处理 34k 请求,但延迟在 2 到 300 毫秒之间。

我们不能说每秒 25k 请求是不够的,特别是我们使用 JavaScript。 如果你的应用/网站需要更多的 JavaScript,那么 Deno 就不是合适的工具。

与 Node.js 的兼容性

由于引入了许多变化,Deno 没有提供与现有 JavaScript 包和工具的兼容性。 正在标准库上创建一个兼容层,但它还没有接近完成。

由于 Node.js 和 Deno 是两个有着共同目标的非常相似的系统,我们希望随着时间的推移,后者能够执行越来越多的 Node.js 程序。 然而,尽管一些 Node.js 代码目前是可运行的,但情况并非如此。

TypeScript 编译速度

正如我们前面提到的,Deno 使用 TypeScript 编译器。 它是运行时中最慢的部分之一,特别是与 V8 解释 JavaScript 的时间相比。 快照确实有帮助,但这是不够的。 Deno 的核心团队认为他们将不得不将 TypeScript 编译器迁移到 Rust 来修复它。

由于完成这项任务需要大量的工作,这可能不会很快发生,即使它应该是一件事情,使其启动时间的数量级更快。

缺少插件/扩展

尽管 Deno 有一个支持自定义操作的插件系统,但它还没有完成,被认为是不稳定的。 这意味着将本地功能扩展到比 Deno 提供的更多的功能实际上是不可能的。

在这一点上,我们应该了解 Deno 目前的局限性以及它们存在的原因。 随着 Deno 的成熟和发展,其中一些问题可能很快就会得到解决。 其他的则是设计决策或路线图优先级的结果。 在决定是否应该在项目中使用 Deno 时,了解这些限制是最基本的。 在下一节中,我们将看一看我们认为 Deno 非常适合的用例。

探索用例

现在您可能已经意识到,Deno 本身有许多与 Node.js 相同的用例。 大多数所做的改变是为了确保运行时更安全、更直观,但由于它利用了大多数相同的技术、共享相同的引擎和许多相同的目标,所以用例之间不会有太大的差异。

然而,即使差异不是那么大,在特定的情况下,可能会有一些小的细微差别使其中一个比另一个更适合。 在本节中,我们将探讨 Deno 的一些用例。

灵活的脚本语言

脚本是解释型语言最出色的特性之一。 当我们想要快速创建原型时,JavaScript 是完美的。 这可以是重命名文件、迁移数据、从 API 中消费某些东西,等等。 它只是感觉像是用例的正确工具。

Deno 非常重视脚本编写。 运行时本身让用户很容易用它编写脚本,因此为这个用例提供了很多好处,特别是与 Node.js 相比。 这些好处包括能够只用一个 URL 执行代码,而不必管理依赖项,以及能够基于 Deno 创建可执行文件。

在所有这些之上,您现在可以导入远程代码,同时控制它使用的权限,这是信任和安全方面的一个重要步骤。

Deno'sRead Eval Print Loop(REPL)是做实验工作的好地方。 再加上我们之前提到的,二进制文件的小尺寸和它包含所有所需工具的事实是蛋糕上的樱桃。

更安全的桌面应用

尽管插件系统还不稳定,并且允许开发人员创建桌面应用的软件包严重依赖于它,但它是非常有前途的。

在过去的几年里,我们看到了桌面网络应用的兴起。 电子框架(https://www.electronjs.org/)的兴起使 VS Code 或 Slack 等应用得以创建。 这些是运行在 WebView 中的网页,可以访问许多人日常生活的一部分的原生特性。

然而,对于安装这些应用的用户来说,他们必须盲目地信任它们。 在前面,我们讨论了安全性以及 JavaScript 代码如何访问它所运行的所有系统。 Deno 在这一点上是完全不同的,因为它的沙箱和所有的安全特性,这要安全得多,而且释放出来的潜力是巨大的。

在本书中,我们将看到在 Deno 中使用 JavaScript 构建桌面应用的许多进步。

一个快速和完整的编写工具的环境

Deno 的特性将其定位为一个非常完整、简单和快速的编写工具的环境。 当我们说到工具时,这不仅仅是针对 JavaScript 或 TypeScript 项目的工具。 由于单个二进制文件包含开发应用所需的所有内容,我们可以在 JavaScript 世界之外的生态系统中使用 Deno。

它的清晰性、通过 TypeScript 自动生成文档、易于运行以及 JavaScript 的流行使 Deno 成为编写工具(如代码生成器、自动化脚本或任何其他开发工具)的合适组合。

在嵌入式设备上运行

通过使用 Rust 并将核心分配为 Rust 机箱,Deno 自动实现了嵌入式设备的使用,从物联网设备到可穿戴设备和 ARM 设备。 同样,它很小并且包含了二进制文件中的所有工具这一事实可能是一个巨大的胜利。

事实上,这个箱子是独立的,允许人们将 Deno 嵌入到不同的地方。 例如,当用 Rust 写数据库,想要添加 Map-Reduce 逻辑时,我们可以使用 JavaScript 和 Deno 来完成。

生成浏览器兼容的代码

如果你之前没有看过 Deno,那么这可能会让你感到惊讶。 我们不是在谈论服务器端运行时吗? 我们是来旅游的。 但同样的服务器端运行时一直在努力保持 API 的浏览器兼容性。 它在其工具链中提供了一些特性,使代码能够在 Deno 中编写并在浏览器中执行,我们将在第七章HTTPS、提取配置和浏览器中的 Deno 中进行探索。

所有这些都由 Deno 团队负责,他们保持 api 与浏览器的兼容性,并生成浏览器代码,这将开启一组尚未发现的新可能性。 浏览器兼容性是我们稍后将使用这本书,在第七章,HTTPS,提取配置,和 Deno 浏览器构建 Deno 应用通过编写一个完整的应用中,客户机和服务器在 Deno。

成熟的 api

与 Node.js 一样,Deno 在处理 HTTP 服务器上投入了大量精力。 有了一个完整的标准库,它为框架提供了可以在其上编写的伟大原语,毫无疑问,that api 是最强大的 Deno 用例之一。 TypeScript 在文档、代码生成和静态类型检查方面是一个很好的补充,有助于成熟的代码库扩展。

在本书的其余部分,我们将更多地关注这个特定的用例,因为我们相信它是最重要的用例之一——Deno 的亮点。

这些只是我们认为 Deno 非常适合的用例中的几个例子。 与 Node.js 一样,我们也知道有许多新的用法有待发现。 我们很高兴能参与这次冒险,并看看它还会推出什么。

小结

在本章中,我们回到 2009 年来理解 Node.js 的创建过程。 在那之后,我们认识到与线程模型相比,为什么以及什么时候应该使用事件驱动方法,以及它带来的优势。 我们开始理解什么是事件异步代码,以及 JavaScript 如何帮助 Node.js 和 Deno 最大限度地利用服务器资源。

在那之后,我们快速浏览了 Node.js 10 多年的故事,它的演变,以及它是如何开始被采用的。 我们观察了运行时是如何增长的,以及它的基础语言,JavaScript,同时帮助数以百万计的企业交付伟大的产品给它的客户。

然后,我们用现代的眼光来看待 Node.js。 生态系统和语言发生了什么变化? 开发人员的痛点是什么? 我们深入研究了这些痛点,并探讨了为什么改变 Node.js 来解决它们是困难和缓慢的。

随着这一章的深入,Deno 的动机变得越来越明显。 在回顾了 JavaScript 在服务器上的过去之后,出现一些新的东西是有意义的——它将解决以前经历的痛苦,同时保留开发人员喜欢的东西。

最后,我们认识了 Deno,他将成为我们这本书的朋友。 我们学习了它的愿景,原则,以及它如何提供解决某些问题的方法。 在先睹为 Deno 的架构和组件提供了可能之后,我们必须讨论一些权衡和当前的限制。

我们通过列出 Deno 非常适合的用例来结束本章。 在本书后面开始编码时,我们将回到这些用例。 从这一章开始,我们的方法将更加具体和实用,始终向您可以运行和探索的代码和示例移动。

既然我们知道了 Deno 是什么,我们就可以开始使用它了。 在下一章中,我们将设置相应的环境并编写 Hello World 应用,以及做许多其他令人兴奋的事情。

冒险就是这样开始的,对吧? 我们走吧!*