六、 NodeJS 中的承诺

在前一章中,我们学习了 WinRT 中的承诺以及如何使用 Microsoft 平台实现这些承诺。 promise 的概念比其他语言的覆盖面更广。 这是开源技术中发展最快的概念之一。

在本章中,我们将讨论一个 JavaScript 的实现,它极大地改变了现代 web 开发的进程,增强了我们实现实时 web 的方式。 这个惊人的技术叫做 Node.js,是谷歌用 JavaScript 编写的基于 V8 引擎的平台。 Node.js 中的承诺比任何其他平台或实现提供的承诺更有趣、更先进、更高效。 让我们深入了解细节,看看 Node.js 对实时网络的承诺是什么。

V8 引擎-机械学

2008 年谷歌首次推出其令人惊叹的浏览器谷歌 Chrome 时,一个只有一级方程式赛车手和跑车制造商知道的术语被引入了网络浏览器。

就像许多现实生活中的产品及其机制被计算机行业复制和描述一样,V8 引擎是最近这种建模的真实例子之一。 由于本书的重点是承诺,让我们简要地看看 V8 引擎在现实中是什么。

V8 发动机是一种非传统的发动机,在曲轴上安装了八个汽缸,以产生额外的马力。 这款发动机比 V6 发动机(另一款 v 型发动机)更平稳,比 V12 发动机更便宜。

谷歌 Chrome 的 V8 引擎

谷歌 Chrome 是一个开放的源代码项目,使谷歌在 web 浏览器竞赛中名列第一。 Chrome 是建立在一个专门设计的 JavaScript 引擎 V8 上。 基于 V8 的 Chrome 在很短的时间内获得了全世界用户的欢迎和关注。 它于 2008 年 9 月 2 日首次推出。 然而,这个 V8 JavaScript 引擎做了什么使它比任何其他程序更快和例外呢? 它没有把高级语言解释器编译成机器代码。 它基本上跳过代码解释的中间部分,然后将高级代码转换为机器代码。 这就是为什么 Chrome 更快的原因:

The V8 engine in Google Chrome

Node.js 的演变

谷歌 Chrome 作为一个开源浏览器发布后,人们开始对它感兴趣。 这种迅速的兴趣有两个主要原因。 从普通用户的角度来看,它比任何其他可用的 web 浏览器都要快得多,从开发人员的角度来看,它彻底改变了浏览器技术,将高级指令立即转换为机器码,去掉了编译器或解释器的完整中间件层。

许多开发人员开始探索代码库,以找到他们所参与的解决方案的可能性,并从这个新的令人惊异的代码库编译中获得最大的好处。

Ryan Dahl 是那些想要尝试 V8 JavaScript 引擎的开发者之一,因为他在 Joyent 工作时正忙于解决一个问题。 问题是让浏览器知道上传过程还剩下多少时间。 受到最近发布的 V8 和 Ruby 的 Mongrel web 服务器的启发,他起草了后来演变成 Node.js 的代码库。

Node.js 简介

Dahl 所创造的是现代 web 应用开发中一个全新概念 Node.js 的第一个版本。

简单地说,它的服务器端 JavaScript 是建立在谷歌的 V8 引擎上的。 Node.js 是一个基于事件的非阻塞 I/O。 它轻量级、高效,最适合在分布式设备上运行的数据密集型实时 web 应用。

下载并安装 Node.js

可从http://nodejs.org/download官网下载 Node.js。

Node.js 可用于各种平台。 选择您的操作系统,安装程序将指导您完成余下的工作。

Node.js 是,也可以在 GitHub 上作为一个开源项目https://github.com/joyent/node,供世界各地的开发人员为其演进和开发做出贡献。

安装说明非常简单,易于理解,只需要遵循与操作系统相关的安装程序即可。 这是相当简单的,过程完成没有太多麻烦。 你所需要做的就是按照屏幕上的说明去做。

节点包管理器- NPM

使用 Node.js 的最佳优点之一是 NPM 或 Node 包管理器。 对于开发人员来说,通过更快地共享代码库来进行思想协作是一种有效的方式。 然而,事实并非如此。 NPM 的最佳用途是下载并安装另一个简单的代码目录。 只需输入简单的命令(例如npm install express),就可以轻松地将整个包下载并安装到您的机器上。 对于每个包,都有一个包含关于包的元数据的.json文件。 在基于 unix 的环境中,Node 包管理器不仅帮助下载和设置其他包,而且还能够更新 Node.js 本身。

NPM 也是 Node.js 在 JavaScript 开发者社区流行的另一个原因。 不像其他语言,上传库和二进制文件非常耗时,而且是面向权限的。 有了 NPM,它是一种更快、更少权限的模式,吸引了开发人员在社区中上传和分享他们的工作。

更多关于 NPM 的信息,请点击https://www.npmjs.com/

在后面的小节中,我们将看到 NPM 如何帮助我们安装和使用我们选择的包,以及使用 NPM 会更快、更流畅。

环境选择

Node.js 在某种程度上是平台独立的,它拥有当前所有主要操作系统可用的所有安装、设置和库。 它适用于所有基于 unix 的操作系统,以及 Mac 和 Windows 平台。 因为我们在这里的主要重点是让你理解 Node.js 和承诺之间的链接是什么,我们将基于 Windows 7(任何版本)平台上的代码示例,因为它是广泛可用的,并且 Node.js 在稳定的条件下也可以在 Windows 7 上使用。 此外,它非常简单,更少的时间。

提示

请记住,使用基于 windows 的系统不会对代码及其输出产生任何影响。 这对于每个操作系统都是一样的,没有任何类型的改变。 您可以毫不犹豫地在任何其他操作系统上轻松使用相同的代码库。

设置 Node.js 环境

让我们熟悉一下环境以及如何使用 Node.js 完成工作。 首先,我们必须知道如何设置程序来编译代码并在机器上运行。

如果你正在阅读本节,这里假设你的机器上已经安装了最新版本的 Node.js; 否则,请参考前面的小节下载和安装 Node.js。

在你设置好 Node.js 之后,通过输入以下命令,检查你的机器上 Node.js 和 NPM 的版本是否可用:

D:\> node –v
D:\> NPM  –v

输出应该类似于下面的截图:

Setting up the environment for Node.js

检查 Node.js 和 NPM 的版本

请注意,Node.js 的当前版本是 0.10.31,NPM 的当前版本是 1.4.23。 我们的示例将基于这些版本,而不是这些版本。

简单节点服务器

现在,我们已经准备好了我们的环境来做一些实验,让我们通过尝试一个简单的节点服务器来完成最明显的活动。 为此,您只需要两个软件。 一个是你的默认文本编辑器,比如 Windows 的记事本,Ubuntu 的 Nano 和网络浏览器。 对于网络浏览器,我们更倾向于使用谷歌 Chrome,因为它很容易在所有平台上使用,并且是本地 Node.js。

所以,在你最喜欢的文本编辑器中输入以下代码:

// simple server written in Nodejs
// This server would be running on default IP http://127.0.0.1
var http = require('http');
http.createServer(function (request, response) 
{
  response.writeHead(200, {'Content-Type': 'text/plain'}); // this defines the MIME type of content
  response.end('Hello World\n'); // this is the browser's output. 
}).listen(1337, '127.0.0.1'); // 1337 is the port number where the browser is listing to this request. 
console.log('Server running at http://127.0.0.1:1337/'); //This line will show at command prompt  

以任何名称保存文件,扩展名为.js。 对于我们的示例,我们使用名称server_example.js。 将此文件保存在一个目录中(例如Promises_in_Node),然后打开终端程序。 对于 Windows,它将是命令提示符。 导航到您保存文件的目录,并键入以下命令:

A simple node server

如果代码没有错误,它将编译并在屏幕上显示以下输出:

A simple node server

现在,打开 Chrome 浏览器,在地址栏中输入http://127.0.0.1:1337/,点击,输入。 这个屏幕将显示 Node.js 服务器的成功输出:

A simple node server

就是这样! 现在可以深入研究 Node.js 中的承诺了。

so far:到目前为止

让我们总结一下到目前为止所学的知识。 我们了解了 V8 引擎是什么,谷歌 Chrome 是如何将其作为 JavaScript 引擎开发的,Node.js 是什么,以及它是如何作为一个成熟的应用开发平台的解决问题的技术开始的。 我们了解了 Node 包管理器以及如何在 Node.js 应用开发中使用它。 然后,我们学习了在哪里下载 Node.js,如何安装它,以及在开发 Node.js 时需要考虑哪些依赖项,最后,我们学习了使用 Node.js 编写一个简单的服务器,并在浏览器中看到它的输出。 这是一个检查点,如果你仍然对 Node.js 感到困惑,请再读一遍,然后继续。

以下章节将让你更多地了解 Node.js 和承诺,以及承诺如何从 Node.js 开发者那里获得如此多的尊重。

Node.js 与 Q 库

第二章JavaScript 异步模型中,我们讨论了什么是回调地狱,以及我们如何处理使用承诺。 每种语言的实现都是不同的。 Node.js 也是同样的情况。 Node.js 中的承诺以不同的方式实现。 在 Node.js 中,承诺不仅用于处理回调地狱,相反,如果函数不能返回值或抛出异常,它可以很容易地传递承诺。 这与我们在前几章中看到的有一点不同。 从 Node.js 的角度来看,promise 是一个表示返回值或抛出异常的对象。此外,它还可以用作远程对象的代理,以提高延迟。

让我们看看下面的代码:

process_one(function (value1) {
    process_two(value1, function(value2) {
        process_three(value2, function(value3) {
            process_four(value3, function(value4) {
                // Do something with value4 
            });
        });
    });
});

乱,不是吗? 它不仅混乱,而且非常混乱和难以维护。 现在,看看下面使用 promise 的代码:

Q.fcall(process_one)
.then(process_two)
.then(process_three)
.then(process_four)
.then(function (value4) {
    // Do something with value4 
})
.catch(function (error) {
    // Error Handler
})
.done();

这代码更混乱,更有效率,和它有一个额外的质量,这是一个隐式的误差传播就像我们try——catchfinally块在 Java 中,捕捉任何不必要的异常并保存项目从完全崩溃时遇到了一个意想不到的情况。

回调方法被称为控制反转,它是一种能够接受回调而不是返回值的函数。 这种机制更容易被描述为这样一句话:别打电话给我,我会叫你

Q 中的承诺有一个非常特殊的趋势,因为它显然使输入参数与控制流参数独立。 只有在使用和创建 api 时才能看到它的真正好处,特别是 variadic、rest 和 spread 参数。

【T0

在简要介绍 Node.js 和 Q 之后,让我们看看如何开发应用。 首先,我们需要获取 Q 库来为它设置模块。

使用 NodePackage Manager 安装 Q 库,如下图所示:

Moving ahead with Q

正如你所看到的,提示说它的q在版本 1.2.0,这是稳定的,也向后兼容。 我们将在本章的所有示例中使用这个版本。

在我们的环境中有了这个安装和过去的升级,我们现在能够对 Q 中承诺给我们的一些常见但富有成效的特性进行取样。

promise 有一个then方法,您可以使用它来获得最终的返回值(实现)或抛出异常(拒绝)。 读过这本书的前几章后,现在我们都知道了。

iPromiseSomething() .then(function (value) { //your code }, function (reason) { //your code } );

下面是前一行代码的工作原理:

  • 如果iPromiseSomething返回一个承诺,该承诺将在稍后用返回值实现,则将调用第一个函数(fulfillment handler)
  • 如果iPromiseSomething函数稍后被抛出的异常拒绝,则会调用第二个函数(拒绝处理程序)来处理异常

正如你所看到的,承诺的解析总是异步的,这意味着实现或拒绝处理程序总是会在事件循环的下一轮被调用(也就是 Node.js 中的process.nextTick)。 这种机制确保它总是在执行实现或拒绝处理程序之前返回一个值。

Q 中的传播

then方法总是返回一个承诺,该承诺要么被处理,要么被拒绝。

在我们的示例代码中,我们将输出赋给reapPromise变量,该变量将保存该值:

var reapPromise  = getInputPromise()
.then(function (input) {
}, function (reason) {
});

reapPromise变量是两个处理器返回值的新承诺。 只有一个处理程序会被调用,它将负责将reapPromise解析为一个函数,该函数只能返回一个值(未来值)或抛出一个异常。

无论情况如何,都会有以下可能的结果:

  • 如果你在处理程序中返回一个值,reapPromise将得到满足
  • 如果在处理程序中抛出异常,reapPromise将被拒绝
  • 如果在处理程序中返回一个承诺,reapPromise将成为该承诺
  • 由于它将成为一个新的承诺,它将有助于管理延迟,合并结果,或从错误中恢复。

如果getInputPromise()承诺被拒绝,而你忘记了拒绝处理程序,错误将转到reapPromise:

var reapPromise = getInputPromise()
.then(function (value) {
});

如果输入的承诺得到满足,而你的履行处理程序失败,值将变成reapPromise:

var reapPromise = getInputPromise()
.then(null, function (error) {
});

当您只对处理错误感兴趣时,Q 承诺提供失败简写:

var reapPromise = getInputPromise()
.fail(function (error) {
});

如果你只是为现代引擎编写 JavaScript,或者使用 CoffeeScript,你可以使用 catch 而不是 fail。

promise 还提供了一个类似于finally子句的fin函数。 当getInputPromise()返回的 promise 返回一个值或抛出一个错误时,将不带参数调用 final 处理程序。

getInputPromise()返回的值或抛出的错误会直接传递给reapPromise,除非最终的处理程序失败,如果最终的处理程序返回一个 promise,可能会延迟:

var reapPromise = getInputPromise()
.fin(function () {
});

简而言之:

  • 如果处理程序返回一个值,该值将被忽略
  • 如果处理程序抛出错误,错误传递给reapPromise
  • 如果处理程序返回一个承诺,reapPromise将被推迟

最终值或错误与立即返回值或抛出错误具有相同的效果; 一个值将被忽略,一个错误将被转发。

所以当我们寻找传播时,我们需要记住我们想从返回值看到什么。 thenfailfin函数是在使用 Q 中的传播时要记住的键。

提示

如果你是为现代引擎编写 JavaScript,你可以使用finally而不是fin

链接和嵌套承诺

还记得在第 2 章,中的承诺链接吗?我们学习了关于链接和回调地狱处理的所有事情。 这与 Node.js 使用 Q 是一样的。

在 Node.js 中,有两种方法可以使用 Q 来链接承诺:一种是在处理程序内部链接承诺,另一种是在处理程序外部链接承诺。

假设我们同时做多个事情,我们可以这样设置承诺链:

f1_promise()
    .then(function() { return s1_promise(); })
    .then(function() { return t1_promise();  })
        ...
    .then(function() { return nth_promise();    });

因此,我们可以说,ANY_promise()函数可以包含一些行为,这将返回一个最终导致返回结果的 promise 对象。 一旦返回真实的结果,它将触发链中的下一个函数。

现在看起来很好,如果你想设置一个异步函数,并在执行链中下一个承诺的行为之前等待结果,该怎么办?

Q 有一个解。 使用.defer()deferred.resolve(),你可以以一种更易于管理和预测的方式获得它。

Q 中的序列

像链接一样,序列是另一种以你想要的方式呈现结果的方法。 序列是您可以以一种预定义的方式使用的方法,以获得所需的情况的结果。 为了更紧密地保持它并生成结果,Q 以一种独特的方式提供序列。

假设您有许多生成承诺的函数,它们都需要按顺序运行。 你可以像下面这样手动操作:

return seq(startValue).then(secondValue).then(thirdValue);

你必须确保每个then()必须与另一个then();在一个序列中,以维持序列。 如果不这样做,将破坏序列,稍后将无法获得另一个值。

另一种方法是动态地指示序列。 这可以更快,但在执行代码时需要更多的注意,因为不可预测的代码可能会损害整个序列。

以下是如何动态完成的代码片段:

var funcs = [startValue, secondValue, thirdValue];

var result = Q(startValue); 
funcs.forEach(function (f) {
    result = result.then(f);
});
return result;

如果这看起来你使用了太多的代码行,使用reduce:

return func.reduce(function (tillNow, f) {
    return tillNow.then(f); 
}, Q(startValue));

Q

有了 Q,你在 Node.js 中有了一个独特的功能,如果你想要组合承诺数组的列表,你可以编写干净且可管理的代码。 这可以帮助您以更易于管理的方式编写更复杂的顺序数据结构。 我们怎么去那儿? 使用all。 考虑下面的例子:

return Q.all([
    eventAdd(2, 2),
    eventAdd (10, 20)
]);

Q.all([func1(), func2()]);函数将是上述代码的通用形式。 你也可以用spread代替then。 我们可以用 Q 替换另一个新东西吗? 不是真的! spread函数将值分散到实现处理程序的参数中。 对于拒绝处理程序,它将获得第一个失败信号。 因此,任何注定首先失败的承诺都会被拒绝处理程序处理:

function eventAdd (var1, var2) { 
    return Q.spread([var1, var2], function (var1, var2) {
        return a + b;
    })
}

Q.spread(). Call all initially

return getUsr() .then(function (userName) { return [username, getUser(userName)]; }) .spread(function (userName, user) {
});

当你调用函数时,它将返回allSettled。 在这个函数中,promise 将作为保存该值的数组返回。 当这个承诺被实现时,数组包含原始承诺的实现值,其顺序与那些承诺相同。 美妙之处在于,如果任何承诺被拒绝,它会立即被拒绝,而不是等待其他人来分享自己的状态:

Q.allSettled(promises)
.then(function (results) {
    results.forEach(function (result) {
        if (result.state === "fulfilled") {
            var value = result.value;
        } else {
            var reason = result.reason;
        }
    });
});

any函数接受一个承诺数组,该数组返回一个承诺,该承诺由第一个给定的承诺来实现,或者在所有给定的承诺都被拒绝的情况下被拒绝:

Q.any(promises).then(function (firstPromise) {
    // list of any of the promises that were fulfilled. 
}, function (error) {
    // All of the promises were rejected. 
});

如何在 Node.js 中处理 Q 中的错误

有时,拒绝发生时,承诺会产生错误。 这些错误非常聪明,可以避开为处理这些错误而分配的处理程序。 所以,我们需要明确地处理它们。

让我们看看下面的代码片段,看看如何处理它:

return scenario().then(function (value) {
    throw new Error("I am your error mesg here .");
}, function (error) {
    // We only get here if scenario fails 
});

为什么会发生这种情况? 假设之间的并行性承诺和try/catch虽然我们试图执行scenario(),错误处理程序代表scenario()catch,而实现处理程序代表了代码之后try/catch。 现在,这个代码需要自己的try/catch块。

try/catch块对你们这些写过一些主要语言代码的人来说并不是一个新概念。 由于 Node.js 是基于 JavaScript 的,并且 Q 正在处理它,语法可能会有一点不同,但功能或多或少是类似以下代码的:

Q.
try(function()
    {return scneario().then(function(value)throw new Error("im your thrown error");)} )
.catch({ function (error)
    {console.error("i am catched",error)} 
});

简单地说,就承诺而言,这意味着您正在链接您的拒绝处理程序。

因承诺而进步

与其他图书馆不同,promise 具有独特的沟通能力。 如果你想让它跟你说话,它可以告诉你它的进展。 最好是,这些通知是由开发人员编写的,可以在指定的时间间隔通知他们进展情况。 我们可以使用我们最喜欢的then()函数:

return uploadFile()
.then(function () {
    // Success uploading the file 
}, function (err) {
    // There was an error, and we get the reason for error 
}, function (progress) {
    // this is where I am reporting back my progress. executed 
});

使用 q 有很多好处。对于这个特定的话题,它提供了一个简短的通话进度,使我们尽量减少了使用*.progress();的努力,只有一条线。

return uploadFile().progress(function (progress) {
    // We get notified of the upload's progress 
});

到达一连串承诺的终点

当我们讨论结束承诺链时,我们必须确保任何错误不会在结束之前得到处理,因为它将被重新抛出并报告。

这是一个临时的解决方案。 我们正在探索使未处理错误可见而无需任何显式处理的方法。

因此,返回如下代码:

return hoo()
.then(function () {
    return "foo";
});
Or we can do It like this:
hoo()
.then(function () {
    return "bar";
})
.done();

我们为什么要这么做? 为什么我们需要这样调用机制? 答案很简单,你必须结束链或者返回到另一个承诺。 这是因为,由于处理程序捕获错误,异常无法被观察到是一种不幸的模式。

每隔一段时间,你就需要从零开始创造一个承诺。 这很正常; 你可以创造自己的承诺,也可以从另一个链条获得。 不管情况如何,把它看作是一个开始。 有很多方法可以让你用 q 创建一个新的承诺,以下是其中一些:

Q.fcall(); 
//Using this function fcall you can create and  call other //functions, along with Promise functions. To do that simply //follow this syntax 
return Q.fcall(function () {
    return 10; 
});

不仅如此,fcall();还可以用来获得一个异常处理的承诺,类似于下面的代码片段:

return Q.fcall(function () {
    throw new Error("I am an error");
});

因为fcall();可以调用函数,甚至是承诺的函数,所以使用eventualAdd();函数来添加两个数字:

return Q.fcall(eventualAdd, 2, 2);

基于回调的承诺 vs .基于 q 的承诺

假设你必须使用基于回调的而不是基于承诺的进行链接,你会有什么选择? 答案是 Q 提供了Q.nfcall()friends();,但大多数时候,我们必须依靠deferred:

var deferred = Q.defer();
FS.readFile("hoo.txt", "utf-8", function (error, text) {
    if (error) {
        deferred.reject(new Error(error));
    } else {
        deferred.resolve(text);
    }
});
return deferred.promise;

通常情况下,我们可以这样实现:

//normal way of handling rejected promises.
deferred.reject(new Error("Can't do it"));
//this is how we do it
var rejection = Q.fcall(function () {
    throw new Error("Can't do it");
});
deferred.resolve(rejection);

延迟、超时和通知

在某些情况下,我们想要使我们的功能的输出比正常情况稍微延迟或慢一些。 这是当我们等待某个事件发生的时候,比如在强度指示器上检查密码的强度。

对于所有这样的需求,Q 提供了一组函数来提供这种控制。 这些函数是:

  • Q.delay()
  • Q.notify()
  • deferred.notify()

上述函数不仅能够在需要时创建延迟,而且还能够在可能发生延迟时发出通知。 如果你想延迟通知,deferred.notify()将达到目的。

Q.delay()

下面的代码是Q.delay的简化实现:

function delay(ms) {
    var deferred = Q.defer();
    setTimeout(deferred.resolve, ms);
    return deferred.promise;
}

Q.timeout()

Asimple way to work withQ.timeout

function timeout(promise, ms) {
    var deferred = Q.defer();
    Q.when(promise, deferred.resolve);
    delay(ms).then(function () {
        deferred.reject(new Error("Timed out"));
    });
    return deferred.promise;
}

deferred.notify()

最后,您可以使用deferred.notify()向承诺发送进度通知。

浏览器中有一个针对 XML HTTP 请求的包装器:

function requestOkText(url) {
    var request = new XMLHttpRequest();
    var deferred = Q.defer();
    request.open("GET", url, true);
    request.onload = onload;
    request.onerror = onerror;
    request.onprogress = onprogress;
    request.send();

    function onload() {
        if (request.status === 200) {
            deferred.resolve(request.responseText);
        } else {
            deferred.reject(new Error("Status code was " + request.status));
        }
    }

    function onerror() {
        deferred.reject(new Error("Can't XHR " + JSON.stringify(url)));
    }

    function onprogress(event) {
        deferred.notify(event.loaded / event.total);
    }

    return deferred.promise;
}

下面是如何使用这个requestOkText函数的示例:

requestOkText("http://localhost:5000")
.then(function (responseText) {
    // If the HTTP response returns 200 OK, log the response text. 
    console.log(responseText);
}, function (error) {
    // If there's an error or a non-200 status code, log the error. 
    console.error(error);
}, function (progress) {
    // Log the progress as it comes in. 
    console.log("Request progress: " + Math.round(progress * 100) + "%");
});

Q.Promise() -创造承诺的另一种方式

Q.Promise是一个可选的承诺创建 API,它具有与延迟的概念相同的能力,但没有引入另一个概念实体。

让我们用Q.Promise来重写前面的requestOkText例子:

function requestOkText(url) {
    return Q.Promise(function(resolve, reject, notify) {
        var request = new XMLHttpRequest();
        request.open("GET", url, true);
        request.onload = onload;
        request.onerror = onerror;
        request.onprogress = onprogress;
        request.send();

        function onload() {
            if (request.status === 200) {
                resolve(request.responseText);
            } else {
                reject(new Error("Status code was " + request.status));
            }
        }
        function onerror() {
            reject(new Error("Can't XHR " + JSON.stringify(url)));
        }
        function onprogress(event) {
            notify(event.loaded / event.total);
        }
    });
}

如果requestOkText抛出异常,返回的 promise 将被拒绝,抛出的异常是拒绝的原因。

Q

承诺对象的类型转换是必须的,你必须在 Q 类型承诺中转换来自不同来源的承诺。 这是因为并不是所有 promise 库都具有与 Q 相同的保证,当然也不提供所有相同的方法。

//using when 
return Q.when(AmIAvalueOrPromise, function (value) {
}, function (error) {
});
//The following are equivalent:
return Q.all([a, b]);
return Q.fcall(function () {
    return [a, b];
})
.all();

大多数库只提供部分功能的then方法。 另一方面,Q 和其他的很不一样:

return Q($.ajax(...))
.then(function () {
});

如果你得到的承诺不是你的库提供的 Q 承诺,你应该使用 Q 函数包装它。 您甚至可以使用Q.invoke();作为速记,如下代码所示:

return Q.invoke($, 'ajax', ...)
.then(function () {
});

作为代理的承诺

关于承诺的一个奇妙之处在于它可以作为另一个对象的代理,不仅是本地对象,也可以是远程对象。 有一些方法可以让您自信地使用属性或调用函数。 所有这些交易所都会返回承诺,这样它们就可以被捆绑起来。

这里是函数列表,你可以使用作为一个承诺的代理:

|

直接操作

|

使用承诺作为代理

| | --- | --- | | value.foo | promise.get("foo") | | value.foo = value | promise.put("foo", value) | | delete value.foo | promise.del("foo") | | value.foo(...args) | promise.post("foo", [args]) | | value.foo(...args) | promise.invoke("foo", ...args) | | value(...args) | promise.fapply([args]) | | value(...args) | promise.fcall(...args) |

如果 promise 是远程对象的代理,可以使用这些函数而不是then()来减少往返。

即使在局部对象的情况下,这些方法也可以用作特别简单的满足处理程序的简写。 例如,您可以替换:

return Q.fcall(function () {
    return [{ foo: "bar" }, { foo: "baz" }];
})
.then(function (value) {
    return value[0].foo;
});

代码如下:

return Q.fcall(function () {
    return [{ foo: "bar" }, { foo: "baz" }];
})
.get(0)
.get("foo");

熟悉 Node.js - Q 方式

当你使用 Node.js 回调模式的函数时,其中回调是以function(err, result)的形式出现的,Q 提供了一些有利的服务函数来在它们之间进行调整。 两个最重要的功能是:Q.nfcall()Q.nfapply():

  • Q.nfcall():调用 Node.js 函数
  • Q.nfapply():Node.js 函数应用

    return Q.nfapply(FS.readFile, ["foo.txt", "utf-8"]);

它们都被用来调用与 Node.js 相似的函数,这样它们就可以生成承诺。

解绑定及其溶液

当您使用方法而不是简单的函数时,很可能会遇到一些常见的问题,例如将一个方法传递给另一个函数——例如Q.nfcall——解除该方法与其所有者的绑定。 Q 也必须在这里提供服务,这样你就可以通过以下两种方式来避免这种情况:

  • 使用Function.prototype.bind()
  • 使用 Q 提供的方法:

    return Q.ninvoke(redisClient, "get", "user:1:id"); // node invoke return Q.npost(redisClient, "get", ["user:1:id"]); // node post

还有另一种方法可以创建可重复使用的包装,使用:

  • 【t】:【t】
  • 【t】:【t】

Q 支持跟踪堆栈

Q 还扩展了对长堆栈跟踪的可选支持; 这有助于开发人员通过提供错误的全部原因和拒绝原因来管理错误的堆栈属性,而不是简单地停止而没有任何有意义或可读的错误。

下面的函数就是这样一个例子,其中错误没有以有意义的方式处理,当有人试图执行这个代码片段时,他/她经历了无意义和不可跟踪的错误:

function TheDepthOfMyCode() {
  Q.delay(100).done(function explode() {
    throw new Error("hello I am your error Stack!");
  });
}
TheDepthOfMyCode ();

这将给我们一个看起来毫无帮助的堆栈跟踪,看起来像这样:

Error: hello I am your error Stack!
 at explode (/path/to/test.js5:166)
 at _fulfilled (/path/to/test.js:q:54)
 at resolvedValue.promiseDispatch.done (/path/to/q.js:923:20)
 at makePromise.promise.promiseDispatch (/path/to/q.js:400:23)
 at pending (/path/to/q.js:397:39)
 at process.startup.processNextTick.process._tickCallback (node.js:244:9)

然而,如果你通过设置Q.longStackSupport = true打开这个功能,那么这将给我们一个好看的有用的堆栈跟踪,看起来像这样:

Error: hello I am your error Stack!
 at explode (/path/to/test.js:3:11)
From previous event:
 at theDepthsOfMyProgram (/path/to/test.js:2:16)
 at Object.<anonymous> (/path/to/test.js:7:1)

与大多数时候不同的是,在 JavaScript 中,我们使用断点或alert()来查看错误发生的位置,这非常令人沮丧和耗时。 Q 不仅为我们提供了一种优雅的方法来确定错误发生的位置,而且还可以读取和分析整个跟踪来解决问题。

提示

在 Node.js 中,这个特性也可以通过Q_DEBUG环境变量来启用:

Q_DEBUG=1 node server.js

这将在每个 Q 实例上启用长堆栈支持。

做出承诺的行动

以 Q 开始,执行返回承诺的动作。 让 Node.js 动作http.get作为承诺动作:

// using-promise.js
var httpGet = function (opts) {
     var deferred = Q.defer();
     http.get(opts, deferred.resolve);
     return deferred.promise;
};

稍后,您可以使用:httpGet(...).then(function (res) {...});,但您必须确保函数返回承诺。 第一个Q.defer()返回一组空承诺及其操作。 deferred.promise是固定一定价值的空承诺:

// promise-resolve-then-flow.js
var deferred = Q.defer();
deferred.promise.then(function (obj) {
    console.log(obj);
});

deferred.resolve("Hello World");

这将打印Hello World到控制台。 通常,你可以转换普通的回调动作:

// promise-translate-action.js
action(arg1, arg2, function (result) {
    doSomething(result);
});

承诺的行动:

// promise-translate-action.js
var promiseAction = function (arg1, arg2) {
    var deferred = Q.defer();
    action(arg1, arg2, deferred.resolve);
    return deferred.promise;
}

promiseAction(arg1, arg2).then(function (result) {
    doSomething(result);
});

对象处理承诺

我们学习了关于承诺如何帮助对象处理这些是本地对象还是远程对象的大量内容。 如前所述,then回调可以以任何方式使用结果。 此外,每个处理都是属性访问或函数调用的原语分解,例如:

// object-unsued.js
httpGet(url.parse("http://abc.org")).then(function (response) {
    return response.headers["location"].replace(/^http:/, "");
}).then(console.log);

原语访问分解

Q 可以分解每个原语访问的连续动作。 让我们看看下面的代码:

// object-decomposed.js
httpGet(url.parse("http://abc.org")).then(function (response) {
    return response.headers;
}).then(function (handlers) {
    return handlers["location"];
}).then(function (location) {
    return location.replace(/^http:/, "");
}).then(console.log);

q 的承诺还有一个好处,它有一个基本访问的支持方法作为承诺。

通过它们,分解的动作也转换为:

// object.primitive.js
httpGet(url.parse("http://example.org"))
    .get("handlers").get("location").post("replace", [/^http:/, ""])
    .then(console.log);

View revisited

view()方法帮助将所有的值镜像到基于 q 的承诺中,没有任何区别,无论是来自一个值还是任何其他函数。 有两种方法可以实现这一点:

  • promise.post(name)
  • promise.send(name)

该将承诺值的方法转换为方法结果的承诺。

结果view()对所有方法都有承诺价值。 您可以在view()then回调中使用view,例如:

// object-view.js
Q.resolve(new Date()).view().then(function (dateView) {
    return dateView.toTimeString().then(function (str) {
        return /\((.*)\)/.exec(str)[0]
    });
}).then(console.log);

放弃承诺

我们之前看到了是如何使用done();的,但在这里,它带来了一个完整的印象。

使用 T0,我们可以结束我们的承诺,中止我们的程序。 我总有办法兑现承诺

then().then().done();

如果承诺被检查(并且之前没有捕获到错误),done()函数强制生成一个无法捕获的错误(例如,setTimeout(function () {throw ex;}, 0))。

在 Node.js REPL 上,运行Q.reject("uncaught").done(),然后带着错误退出。

如果错误达到了done()函数,您可以认为它只是一个编程错误(而不是异常状态)。

用于 Node.js 的 Q 实用程序

在这一章中,我们了解到承诺在 Node.js 中越来越容易使用。 下面是 Q 为使用 Node.js 提供的所有主要实用程序集:

  • Q.nfapply(fs.readFile, [filename, encoding]).then(console.log);
  • Q.nfcall(fs.readFile, filename, encoding).then(console.log);
  • Q.nfbind(fs.readFile)(filename, encoding).then(console.log);
  • Q.npost(fs, "readFile", [filename, encoding]).then(console.log);
  • Q.nsend(fs, "readFile", filename, encoding).then(console.log);

Q 提供了更多的功能,但前面的这些功能是最好的、最常用的、最合理的,可以帮助我们编写一个更易于管理、更清晰和动态控制的机制。

小结

这一章从头到尾都是一段奇妙的旅程,它从一开始就教会了我们关于 Node.js 的知识。 我们没有选择用计算机科学术语来解释这些内容,而是转向了 V8 引擎的机械部分,从那里我们看到了现实世界中的物体如何映射到计算中。

我们了解了什么是 Node.js,从哪里开始这个最惊人的库,谁建立了它,为什么和如何帮助我们创建实时 web 应用。

然后我们转向 Q,这是向 Node.js 提供承诺的最佳方式。 我们看到了如何安装 Q,以及在 Node.js 中使用 Q 的不同方法。 我们还实现了使用 Q 作为 Node.js 承诺实现的目的。

本章将鼓励你开始开发 Node.js,特别是如何利用 Q 作为 Node.js 承诺库。

在下一章,我们将深入探讨 Angular.js 的世界,以及它是如何实现承诺的。