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

JavaScript 中的 Promise - 过去、现在和未来

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2016 年 6 月 26 日

CPOL

8分钟阅读

viewsIcon

19072

一篇关于 JavaScript Promise 的历史、当前趋势和未来的文章

引言

今天,对于任何体面的 Web 应用程序,我们都无法想象编写非异步的 JavaScript 代码。无论是服务调用、动画,甚至是像在网页上显示时间这样简单的事情,我们都必须利用 JavaScript 的异步元素。那么,同步编程和异步编程究竟是什么?最简单的区别是,同步代码必须等待前一个操作完成后才能执行下一个操作。

同步代码通常在单个线程中运行。异步代码必须在单独的线程中执行。当异步 JavaScript 代码运行时,浏览器会为我们处理简单场景下的线程管理。当我们必须等待结果才能继续执行任何任务时,我们总是需要编写异步代码;例如,当我们向服务器发出 Get 请求时,我们就必须等到结果返回给我们并在页面上显示它们。这种等待的需求导致了回调的出现。

回调只是在任何异步请求的结果检索后被调用的代码片段。回调可以有两种类型:成功回调和失败回调。当成功接收到结果时,将执行成功回调。当异步代码执行出现任何问题时,将执行失败回调。在 JavaScript 中,成功回调和失败回调可以是相同或不同的函数,这取决于我们的偏好。

当我们处理异步代码和回调时,Promise 就出现了。Promise 是我们将来会收到并且可能处于多种状态的东西;最常见的状态是 Promise 已完成 (Promise Fulfilled) 和 Promise 被拒绝 (Promise Rejected)

根据 github/kriskowal

引用

如果一个函数无法在不阻塞的情况下返回值或抛出异常,那么它可以返回一个 Promise。Promise 是一个对象,它表示该函数可能最终提供的返回值或抛出的异常。Promise 还可以用作远程对象的代理,以克服延迟。

您可以在这里阅读有关 Promise 的 Commonjs 规范

过去

XML Http Request

在拥有 jQuery 的 Promise 之前,为异步服务调用分配回调的常规方法是使用 XHR 回调。可以使用指定的状​​态码将 Xhr 回调分配给服务器的成功和失败响应。

以下是 xhr 对象的基本实现。在最简单的情况下,我们将创建一个新的 xhr 对象,然后发送请求。在发送请求之前,我们可以根据请求的就绪状态和状​​态分配回调。

if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp = new XMLHttpRequest();
}
else
{// code for IE6, IE5
    xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
xmlhttp.onreadystatechange = function ()
{
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
    {
        successCallback();
    }
}
xmlhttp.open('GET', url, true);
xmlhttp.send();

就绪状态可以具有以下值

  • 0 = 未发送
  • 1 = 已打开
  • 2 = 已接收标头
  • 3 = 正在加载
  • 4 = 已完成

XHR 状​​态可以具有以下值

  • 200 = 确定
  • 404 = 页面未找到

在当前版本中,我们可以为 xhr 事件分配事件监听器。

var xhr = new XMLHttpRequest();
xhr.addEventListener("progress", progress);
xhr.addEventListener("load", complete);
xhr.addEventListener("error", failed);
xhr.addEventListener("abort", cancelled);

回调可以是普通函数

function completed(data) {
    document.getElementById('container').innerHTML = data.Text;
}

jQuery 中的回调

jQuery 的 $.ajax() 具有成功和错误回调,这些回调被广泛用于管理服务器返回数据的延迟问题。它的实现很简单;我们分配函数的两个 JavaScript 属性,这些函数稍后在成功获取结果或发生错误时被调用。

$.ajax({
    method: "POST",
    url: "some.php",
    data: { name: "John", location: "Boston" },
    error: function (xhr, error) {
        //handle the error
    },
    success: function (data) {
        //handle the returned data
    }
});

现在

.then(fn)

快进到今天,我们在 JS 代码中到处都能看到 .then()。我敢打赌,在每个中到大型应用程序中,只要使用了不错的 JS 框架,你都会看到这种实现。顾名思义,.then() 用于在异步代码执行返回响应后执行回调代码。

以下是使用 Deferred Object 的 .then() 函数的 jQuery 实现

$.when($.ajax("test.aspx")).then(function (data, textStatus, jqXHR) {
    alert(jqXHR.status);
});

链式回调

在基于一个异步代码块执行的结果,我们需要发起另一个异步请求的情况下,可能会有很多种情况。在这种情况下,我们必须将回调一个接一个地链接起来。要实现回调链,我们需要一种特殊的对象,它可以存储回调信息,然后排队执行。

$.when($.ajax("someeUrl.aspx"))
    .then(function (data, textStatus, jqXHR) {
        $.ajax("someOtherUrl.aspx");
    })
    .then(function (data, textStatus, jqXHR) {
        $.ajax("someOtherUrl.aspx");
    })
    .then(function (data, textStatus, jqXHR) {
        $.ajax("yetAnotherUrl.aspx");
    }
);

Deferred Object

这个对象可以促进回调链及其排队执行。Deferred 对象公开了几个函数,可用于链接成功和失败回调。其他功能可能包括获取 deferred 对象的当前状态以及在特定上下文中拒绝和解析。

jQuery 中的 Promise 实现

jQuery 从多个 API 返回 Deferred 对象,这些 API 涉及某种异步代码执行。例如,$.ajax() 和许多动画函数会返回 Deferred 对象,我们可以将更多的回调链接到这些对象。

$("#container").css("color", "red").slideUp(2000).slideDown(2000);

其他 JS 代码库

如果我们不想使用任何框架固有的 Promise 代码结构,还有其他代码库可供我们使用。这些库的工作方式或多或少相同,即为成功和失败响应将回调一个接一个地链接起来。其中最突出的一个是 $q

AngularJS 实现了 $q 来处理异步代码执行,并提供 $q 依赖项作为内置服务,我们可以在自定义服务工厂中使用它。

$q 或任何其他此类 Deferred 对象库的目的是修复横向增长的回调链,并将它们转换为线性代码语句。

function callback1 (data) {
    function callback2(data) {
        function callback3(data) {
            function callback4(data) {
                //and so on
            }
        }
    }
}

上面的代码可以使用 Deferred 对象将它们转换为线性链,以链接 Promise。

someFunctionReturningPromise()
    .then(callBack1)
    .then(callBack2)
    .then(callBack3)
    .then(callBack4);

未来

当我们谈论 JavaScript 中 Promise 的未来时,可以说未来已经到来。阻止最新 Promise 实现投入使用的唯一因素是开发者社区的广泛使用。随着越来越多的公司和团队转向 ES6/ES7 编码标准,这种情况也在发生变化。

ES6 Promises

ES6 为 Promise 对象提供了标准规范,可用于处理操作的未来结果。Promise 构造函数的语法如下

new Promise( /* executor */ function(resolve, reject) { ... } );

在上面,我们必须提供一个执行器函数。当我们调用 Promise 对象的 then() 方法时,将执行执行器函数。resolvereject 参数是我们传递给执行器的函数,用于处理任务的未来结果。

下面是一个非常简单的示例,展示了如何使用它

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <div id="container"></div>
    <script>
        var myPromise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve('Hello');
            }, 5000);
        });
        myPromise.then(function (message) {
            document.getElementById('container').innerHTML = message;
        });

    </script>
</body>
</html>

上面的代码将在 5 秒后在容器 <div> 元素中打印我们选择的消息。这种语法仍然与大多数浏览器不兼容,因此如果您决定在应用程序中使用它,则必须为旧版本使用 polyfill。

async 函数

您可能已经在 C# 语言中编写过带有 async 关键字的方法。带有 async 关键字的方法/函数在其主体中使用 await 关键字来等待异步代码执行的结果。如果收到结果,则执行下一组代码,如果未收到,则错误会被 catch 块捕获。这种实现消除了为异步请求/响应任务执行编写单独的回调函数以进行链接的需要。

<script>
    async function GET(url) {
        var result = await getResuts(url);
        return result;
    }
</script>

Async 函数在浏览器中尚无法使用,除非使用合适的转换器对 JS 代码进行转换。JavaScript 中 async-await 的合适替代品是生成器函数。

<script>
    function* someGennerator(url){
        yield get(url);
    }

    console.log(someGenerator('account/1').next().value);
</script>

不幸的是,生成器的支持仍然非常有限,在我们看到它在浏览器中广泛使用之前还需要一段时间。

JavaScript 中 Promise 的高级实现还有很长的路要走,直到事情才能变得稳定。但好消息是,事情正在为我们快速发展。Node.js 环境中已经支持这些功能。对于浏览器,有许多不错的转换器,如 Babel,我们可以使用它们来编写和利用新的语法。

在过去几年里,我看到许多开发者对于 Promise 是什么以及如何利用它们,不仅仅是在异步服务器调用中,而是在许多不同的领域,仍然不是很清楚。

自定义实现

如果您雄心勃勃,那么您甚至可以尝试实现自己的 promise 对象。我以前创建了一个自定义的 Deferred 对象,它支持链接多个回调

我建议使用一个好的 promise 库,而不是编写自己的实现,因为编写自定义代码来处理如此复杂的规范是风险的,而且最好使用经过高度测试、并且在开发者社区中得到广泛使用和支持的代码模块。

各位,今天就到这里。关于 Promise 还有很多可以涵盖的内容;如果您是新手,请将本文视为开始使用 JavaScript 异步代码的起点。如果您是专家,这可能对您来说是一次复习。欢迎提出有意义的评论或提问。

历史

  • 2016 年 6 月 26 日:初始版本
© . All rights reserved.