65.9K
CodeProject 正在变化。 阅读更多。
Home

通过地理定位迁移到模块化 Promise

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2016 年 6 月 20 日

CPOL

14分钟阅读

viewsIcon

15131

从回调和消息传递转换为 Promise

很久以前,我儿子想在路边卖柠檬水。你知道故事怎么讲的:我们去商店,买了原料,然后做了柠檬水。然后他和他的小妹妹坐在路边。这是一个非常棒的第一天,他们一天就赚了大约 90 美元的柠檬水销售额!唯一的缺点是……他一整天都进进出出,想确认他的账算得对不对。“爸爸,我收的钱够吗?”、“爸爸,这看起来是正确的金额吗?”、“爸爸,你能去商店给我买更多柠檬水和杯子吗?” 一天结束时,我们三个人一起讨论构建一个应用程序,帮助他们跟踪财务状况。他们认为,如果其他朋友也能使用这个应用程序,并且跟踪他们卖柠檬水的位置以及销售数量,那就太好了。

问题所在

在构思这个应用程序时,我想到了一些事情。首先,在浏览器中,我们需要与地理定位 API 交互,以获取设备的纬度和经度。这种浏览器交互本质上是异步的。其次,这种定位用户的功能应该是可重用的,以便在其他项目中也能使用。第三,我们需要为主要的 JavaScript 代码与模块化的地理定位模块进行交互指定一种架构(例如,回调、消息传递或 Promise)。

我今天将展示的所有示例都使用 Visual Studio Code 编写,这是一款适用于 Mac、Linux 或 Windows 的轻量级代码编辑器。

我在哪里:地理定位

所有 现代浏览器 都支持 地理定位 API获取 设备位置。地理定位 API 是基于回调的,这意味着作为开发者,我们需要将函数传递给 API,当 API 完成处理时会调用这些函数。地理定位是异步的,这意味着可能需要 100 毫秒或 100 秒……无法知道需要多长时间。我们知道的是,当浏览器完成处理地理定位请求时,传递的回调函数将被执行。

地理定位的开端…… 地理定位演示

地理定位通过 window.navigator 对象访问。geolocation.getCurrentPosition 方法接受 3 个参数。为简单起见,我们将只使用前 2 个参数;一个是在找到设备位置时调用的函数(即 resolveLocation),另一个是在无法访问设备位置时调用的函数(即 rejectLocation)。

成功的交互…… 地理定位演示

请注意,在第 10 行,resolveLocation 回调函数会从浏览器获得一个 position 对象。除其他外,此 position 对象包含所需的纬度和经度属性。它们可以通过 position.coords 对象访问。

啊,拒绝…… 地理定位演示

第 27 行的 rejectLocation 回调会从浏览器获得一个 error 对象。此 error 对象包含错误代码和错误消息。错误代码是数字的,范围从 0 到 3。4 种不同的错误代码在第 30 行的 ERROR_TYPE_CODES 推断常量数组中定义。值得注意的是,类型代码为 0 或 2 的错误包含额外信息,因此 errorMessage 字符串会串联 error.codeerror.message

重用:Revealing Module Pattern(揭示模块模式)

在未来的文章中,我们将深入讨论模块。在此次讨论中,我们将简单地使用 Revealing Module Pattern。它将允许我们将地理定位交互封装到一个模块中,该模块可以在任何项目中,包括您当前正在处理的任何项目中使用。

用于封装和重用的模块…… 揭示模块模式演示

让我们从内到外剖析这段代码。私有的/内部的 getLocation 函数(第 12 行)将是执行与浏览器进行地理定位查找的函数。此私有函数通过模块 API(即第 17 行的 return 块)公开。return 块只是一个 JavaScript 对象,用于指定公共方法与它们隐藏的私有方法之间的关系。

请注意,内部的 getLocationreturn 块被包裹在立即调用的函数表达式(即 IIFE)中。最重要的是,IIFE 有一个函数,浏览器在解析完成后会调用它。这种调用通过第 21 行的 () 来实现。其次,由于 IIFE 返回模块 API,因此会形成一个闭包。这个闭包允许模块的状态(即函数的执行上下文)在整个应用程序的生命周期内存在!这个想法似乎是单例式的,因为这个模块只创建一次;然而,这并不是单例模式。揭示模块在解析后立即实例化,而单例直到首次使用才实例化(例如,调用其 getInstance)。

揭示模块模式的核心允许封装私有变量,这些变量只能通过公开定义的 getter 和 setter 来访问和修改。在此位置模块中,内部/私有的 getLocation 函数不能直接访问。只有通过调用 public DI.location.getLocation 方法,才能调用作用于匿名函数的内部/私有 getLocation 函数。

最后,我们在第 7 行创建一个命名空间。在 DevelopIntelligence 中,我们使用 DI 作为我们的命名空间对象。这使得所有 DevelopIntelligence 代码都只能存储在附加到全局 JavaScript window 对象的一个对象上,从而最大限度地减少了全局对象的污染。

与外部模块交互…… 主调用代码

main.js JavaScript 文件通过 DI 命名空间调用位置模块的公共 getLocation 方法,该方法继而执行私有 getLocation 方法的功能。

模块化地理定位

到目前为止,我们已经了解了如何在浏览器中使用地理定位。我们也学习了如何通过揭示模块模式编写模块。让我们将它们结合起来,以便我们能够真正地模块化地理定位交互。

模块化地理定位…… 位置模块演示

在上面的代码中,对地理定位 API 的调用已插入到私有的/内部的 getLocation 函数(第 9 行)中。以这种方式构建模块可以抽象出处理地理定位的实现细节。它还允许 location-module.js 文件在任何项目中被使用,而无需重复 resolveLocation 和 rejectLocation 函数的实现细节。

通信:模块,说话!

我们在此设置中存在一个明显的问题。

与地理定位的异步特性相对于模块交互。当 main.js 文件调用地理定位模块时,我们还没有定义一种方法可以将经纬度属性从模块传递回主调用代码。此时,我们只能告诉位置模块执行工作,但无法知道它何时完成以与数据进行交互。让我们来看看几种处理这种情况的架构方法:首先是回调,然后是消息传递,最后是 Promise。

回调……

回调提供了主调用代码与地理定位模块之间的保证调用和响应。但是,这种保证的交互是以紧密耦合为代价的。

当我们使用地理定位 API 时,我们在实际应用中已经看到了回调架构。请记住,当我们调用 window.navigator.geolocation.getCurrentPosition 函数时,我们必须传入一些函数,这些函数将在 API 完成异步代码处理时被调用。这是紧密耦合的,因为调用代码需要了解如何处理传递给成功回调的 position 对象以及如何处理传递给失败回调的错误代码和消息的实现细节。

基于回调的地理定位方法…… 回调调用代码示例

在上面的代码(第 18 行)中,我们的主 JavaScript 功能将一个回调函数引用传递给模块。当模块完成其异步代码时,它将执行此回调。基于回调的架构的优点在于保证传递的回调函数引用将在模块认为有必要时执行。

基于回调的地理定位方法(续)…… 回调模块代码示例

位置模块不再仅仅通过控制台打印位置坐标。而是在第 44 行,模块执行了传递给 getLocation 函数签名的 successCallback 回调函数引用。请记住,实际将要调用的函数是位于 index.js JavaScript 文件中的 injectCoordinates。当 injectCoordinates 函数执行时,经纬度将被注入到 HTML 中。

回调为主要 JavaScript 和被调用的模块之间提供了保证的通信架构。但是,这种架构在可读性、可理解性和耦合性方面付出了代价,因为主要 JavaScript 将应用程序的控制权交给了模块,以便在未来的某个时间点运行回调。

消息传递……

消息传递为我们提供了一个松耦合的架构,允许所有实现细节隐藏在模块内部。但是,这种松耦合的架构以无法保证模块对主调用代码的响应为代价。

在消息传递架构中,主调用代码只需要知道如何从模块请求数据以及如何监听响应。在模块内运行异步代码后,会触发一个包含数据的事件,允许所有监听代码都有机会与数据交互。

基于消息传递的地理定位方法…… 消息传递调用代码示例

利用 自定义 JavaScript 事件 来创建消息传递架构。为了实现更松散的耦合,位置模块将公开模块创建的自定义事件类型。在第 23 行,主调用代码存储自定义事件类型,然后在第 26 行,它注册一个针对该特定事件类型的事件监听器。

当位置模块完成其异步代码时,它将触发自定义事件,并且已注册的事件监听器的回调函数将被调用。在成功的交互中(第 28-29 行),injectCoordinates 函数将被调用,并将经纬度插入 HTML。

基于消息传递的地理定位方法…… 消息传递模块代码示例

如上所述,基于消息传递的位置模块将通过 getEventType 函数公开一个自定义事件类型,并通过 getLocation 函数公开位置。这允许位置模块更改事件类型,而无需更改主 JavaScript 文件中的任何代码。换句话说,调用此模块的代码不需要知道它是一个类型为 location 的自定义事件。

基于消息传递的地理定位方法(续)…… 消息传递模块代码示例

位置模块将创建一个新的自定义事件,该事件传递从地理定位交互中检索到的数据。它利用 CustomEvent 构造函数,传入事件类型(即 location)和一个设置为 detail 属性的消息对象(第 66 行)。resolveLocation(成功的回调)创建一个消息对象,其中包含 coordinates.latitudecoordinates.longitude 属性,以及一个布尔 success 属性,设置为 true。

创建自定义事件后,模块然后触发/分派该事件。在第 67 行,dispatchEvent 方法通过 document.body 元素调用,以触发创建的自定义事件。一旦事件被触发,浏览器就会运行在主 JavaScript 文件中先前注册的事件处理程序回调函数,该函数进而将纬度和经度插入 HTML。

Promises

Promise 提供给我们回调和消息传递架构的优点。它们能够保证数据的传递,同时又不强制了解实现细节。在模块异步代码运行后,数据通过预定义的 API 返回给调用代码。

基于 Promise 的模块与基于回调的模块不同,因为它从不控制应用程序。调用代码请求 Promise 模块执行某项操作,并在完成后将收集到的数据返回给调用代码。Promise 模块不负责将方法调用回调用代码。这意味着它不需要保留对主调用代码中定义的特定函数的引用,从而比基于回调的架构具有更松散的耦合架构。

基于 Promise 的模块与基于消息传递的模块不同,因为 Promise 架构保证会向调用对象响应。调用代码请求 Promise 模块执行某项操作,并且立即将一个事务收据(即 Promise 对象)返回给调用代码。在事务收据上,调用代码设置一个回调函数,该函数将在 Promise 模块完成时运行。这意味着无需猜测或希望调用代码已将正确的事件监听器类型注册到正确的 DOM 元素,从而保证调用代码在准备好时获得所需的数据。此外,Promise 架构提供了一个预定义的、一致的 API,消除了处理模块数据的异步数据返回时的猜测。

基于 Promise 的地理定位方法…… Promise 调用代码示例

许多不同的框架都利用了基于 Promise 的架构。jQuery 使用 Deferred Object,Kris Kowal 创建了 q 库,Angular 在 $q 服务 中使用了该库的一种形式,现在 ES6/ES2015 也内置了 Promise

上面我们正在与 ES6/ES2015 Promise 进行交互。要查看其他 Promise 的实际应用,请访问我们的 DevelopIntelligence GitHub 仓库中的 Promise。请注意,在调用代码的第 18 行,调用了 DI.currentLocation 模块。正如我之前所说,立即返回了一个事务收据(即 Promise 对象)。这个 Promise 对象公开了供调用代码交互的标准 API 方法。

Promise API 中最常用的方法是 .then 方法。它被所有 Promise 框架实现支持。在 .then 方法中,我们指定了将在 DI.currentLocation 模块完成其异步代码处理时运行的函数。在这种情况下,当浏览器解析出纬度和经度时,DI.currentLocation 模块通过 .then 方法中的一个参数将该数据作为对象返回给调用代码。顺便说一句,与在 .then 方法中返回的数据对象进行交互可以称为“解包 Promise”。

基于 Promise 的地理定位方法…… Promise 模块代码示例

现在是时候看看如何创建 Promise 而不是回调或消息传递了。有了新的 ES6 / ES2015 Promise 对象 内置功能,我们可以使用 new 运算符从 Promise 构造函数创建 Promise 对象实例,如第 31 行所示。在 Promise 构造函数中,我们定义了一个接收 resolvereject 回调方法的匿名函数。

回想一下上面的回调架构示例。在 Promise 模块(即地理定位模块)内部,有类似的回调方法,它们被定义为 successCallbackfailureCallback。当异步数据可供交互时,应调用 resolvereject 回调。换句话说,当浏览器获取其当前位置时,将调用 resolve 方法,并将坐标数据发送出模块。此时,将在调用脚本中定义的 .then 方法将被触发,位置数据将被处理并放置在浏览器上。

http://promise-blog.azurewebsites.net/promise-es6/ 查看 Promise 的实际应用

要点

与异步数据交互会给应用程序带来复杂性。在与设备的地理定位交互时,我们作为开发者,不得不依赖于何时将数据提供给我们的应用程序进行处理。Promise 提供了一个方便的、预定义的 API 来管理异步复杂性。它们为开发人员提供了一种方法,兼具消息传递架构的松耦合和回调架构的保证。

本文是 Microsoft 技术布道师、工程师和 DevelopIntelligence 关于实际 JavaScript 学习、开源项目和互操作性最佳实践的 Web 开发系列文章的一部分,包括 Microsoft Edge 浏览器。 DevelopIntelligence 为技术团队和组织提供指导式 JavaScript 培训React 培训

我们鼓励您跨浏览器和设备进行测试,包括 Microsoft Edge – Windows 10 的默认浏览器 – 使用 dev.microsoftedge.com 上的免费工具,包括 Internet-scale data(一个强大的工具,用于了解哪些属性对真实 Web 很重要,以及哪些 API 在不同浏览器之间是互操作的)。另外,请 访问 Edge 博客,随时了解 Microsoft 开发人员和专家的最新信息。

© . All rights reserved.