Promises 与 JavaScript 异步编程

发布于 2018-06-28 10:17:56 字数 3483 浏览 1497 评论 0

在如今都追求用户体验的时代,Ajax 应用真的是无所不在。加上这些年浏览器技术、HTML5 以及 CSS3 等的发展,越来越多的富 Web 应用出现,在给与我们良好体验的同时,Web 开发人员在背后需要处理越来越多的异步回调逻辑,这篇文章整理了我认为比较重要的一些点以及容易理解错的地方,使大家对 Promise 对象以及异步编程有更深的认识。

嵌套式回调

setTimeout(function() {
    setTimeout(function() {
    // do something
    }, 10)
}, 100);

$.ajax(url, function() {
        $.ajax(url2, function() {
                $.ajax(url3, function() {
            // do something
        });
    });
});

可是问题来了,当我们的嵌套越多,代码结构层级会变得越来越深。首先是阅读上会变得困难,其次是强耦合,接口变得不好扩展。我们需要一种模式来解决这种问题,这就是 Promises 所要做的事情。

异步函数类型

Javascript里异步函数可以分为两大类型:

  • I/O函数(Ajax、script)
  • 计时函数(setTimeout、setInterval、setImmediate)

异步函数异常捕获

try {
    setTimeout(function A() {
        setTimeout(function B() {
            setTimeout(function C() {
                throw new Error('Error');
            }, 0);
        }, 0);
    }, 0);
} catch (e) {}

运行以上代码,A、B、C 被添加到事件队列里,异常触发时,A、B 已被移出事件队列,内存堆栈里只存在 C,此时的异常不被 try 捕获,只会流向应用程序未捕获异常处理器。

所以在异步函数里,不能使用 try/catch 捕获异常。

分布式事件

JavaScript 的事件核心是事件分发机制,通过对发布者绑定订阅句柄来达到异步相响应的目的:

document.onclick = function() {
    // click
};

PubSub(Publish/Subscribe 发布/订阅)模式,就是这么一种模式,通过订阅发布者的事件响应来达到多层分发解耦的目的。

以下是一个简单版本的 PubSub 模式实现:

var PubSub = (function() {
    var _handlers = {};
    return {
        // 订阅事件
        on: function(eventType, handler) {
            if (!_handlers[eventType]) {
                _handlers[eventType] = [];
            }
        if (typeof handler == 'function') {
            _handlers[eventType].push(handler);
        }
        },
        //发布事件
        emit: function(eventType) {
            var args = Array.prototype.slice.call(arguments, 1);
            var handlers = _handlers[eventType] || [];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i].apply(null, args)
            }
        }
    };
})();

Promises/A 规范

CommonJS 之 Promises/A 规范是 Kris Zyp 于 2009 年提出来的,它通过规范 API 接口来简化异步编程,使我们的异步逻辑代码更易理解。

遵循 Promises/A 规范的实现我们称之为 Promise 对象,Promise 对象有且仅有三种状态:unfulfilled(未完成)、 fulfilled(已完成)、failed(失败/拒绝),而且状态变化只能从 unfulfilled 到fulfilled,或者 unfulfilled 到 failed。

Promise 对象需实现一个 then 接口

then(fulfilledHandler, errorHandler, progressHandler)

then 接口接收一个成功回调(fulfilledHandler)与一个失败回调(errorHandler),progressHandler 触发回调是可选的,Promise 对象没有强制去回调此句柄。

then 方法的实现需要返回一个新的 Promise 对象,以形成链式调用,或者叫 Promise 管道。

为了实现状态的转变,我们还需要实现另外两个接口:

  • resolve:实现状态由未完成到已完成
  • reject:实现状态由未完成到拒绝(失败)

这样子我们开篇所说的嵌套式回调就可以这样子写了:

// 这里假设 Promise 是一个已实现的 Promise 对象
function asyncFn1() {
    var p = new Promise();
    setTimeout(function() {
        console.log(1);
        p.resolve(); // 标记为已完成
    }, 2000);
    return p;
}
function asyncFn2() {
    var p = new Promise();
    setTimeout(function() {
        console.log(2);
        p.reject('error'); // 标记为拒绝
    }, 1000);
    return p;
}

asyncFn1()
    .then(function() {
        return asyncFn2();
    }).then(function() {
        console.log('done');
    }, function(err) {
        console.log(err);
    });

有了 Promise,我们可以以同步的思维去编写异步的逻辑了。在同步函数的世界里,有 2 个非常重要的概念:

  • 有返回值
  • 可以抛出异常

Promise 不仅仅是一种可以链式调用的对象,更深层次里,它为异步函数与同步函数提供了一种更加直接的对应关系。

上面我们说过,在异步函数里,不能使用 try/catch 捕获异常,因此也不能抛出异常。有了 Promise,只要我们显式定义了 errorHandler ,那么我们就可以做到像同步函数那样的异常捕获了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84935 人气
更多

推荐作者

遥远的她

文章 0 评论 0

情深如许

文章 0 评论 0

18120987591

文章 0 评论 0

女皇必胜

文章 0 评论 0

13002228876

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击“接受”或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。