前些天我在团内做了一个关于AngularJS的分享。由于AngularJS大量使用Promise,所以我把基于Promise的异步编程也一并介绍了下。很多东西都是一带而过,这里再记录下。

AngularJS内置的是Kris Kowal的Q框架,我介绍的是自己用得更多的cujoJS的when.js,两者都是Promises/A规范的实现。when.js很小,压缩后只有数kb,gzip后的大小几乎可以忽略。在Node和浏览器环境里都可以使用when.js,唯一需要注意的是:在浏览器使用时,要先加几行代码,再引入js文件。

首先,我们看一小段代码(在浏览器中预览):

JSvar getData = function(callback) {
$.getJSON(api, function(data){
callback(data[0]);
});
} var getImg = function(src, callback) {
var img = new Image(); img.onload = function() {
callback(img);
}; img.src = src;
} var showImg = function(img) {
$(img).appendTo($('#container'));
} getData(function(data) {
getImg(data, function(img) {
showImg(img);
});
});

这段代码完成了三个任务:1)获取数据;2)加载图片;3)显示图片,其中,任务1和2是异步,3是同步,使用的是最常见的callback机制来处理异步逻辑,好处是浅显易懂,缺点是强耦合、不直观、处理异常麻烦等等。

另外一种常见的实现异步编程的方案是事件监听器,例如使用QWrap的CustEvent,让任务成功时fireEvent,那么注册了这个Event的监听器就可以收到这个事件,并收到事件传递过来的数据,Dom标准事件也是采用的这种形式。这种方案也很好理解,代码从略。事件监听可以解耦,可以绑定任意多个监听器,但是依然不直观,而且事件发生之后再绑定的监听器也得不到触发。

我们尝试用when.js改写下这段代码(在浏览器中预览):

JSvar getData = function() {
var deferred = when.defer(); $.getJSON(api, function(data){
deferred.resolve(data[0]);
}); return deferred.promise;
} var getImg = function(src) {
var deferred = when.defer(); var img = new Image(); img.onload = function() {
deferred.resolve(img);
}; img.src = src; return deferred.promise;
} var showImg = function(img) {
$(img).appendTo($('#container'));
} getData()
.then(getImg)
.then(showImg);

看最后三行代码,是不是一目了然,非常的语义化?来看下改写后的任务1、2多了些什么:

var deferred = when.defer();

定义了一个deferred对象。

deferred.resolve(data);

在异步获取数据完成时,把数据作为参数,调用deferred对象的resolve方法。

return deferred.promise;

返回了deferred对象的promise属性。

在Promises/A规范中,每个任务都有三种状态:默认(pending)、完成(fulfilled)、失败(rejected)。

  • 默认状态可以单向转移到完成状态,这个过程叫resolve,对应的方法是deferred.resolve(promiseOrValue);
  • 默认状态还可以单向转移到失败状态,这个过程叫reject,对应的方法是deferred.reject(reason);
  • 默认状态时,还可以通过deferred.notify(update)来宣告任务执行信息,如执行进度;
  • 状态的转移是一次性的,一旦任务由初始的pending转为其他状态,就会进入到下一个任务的执行过程中。

有人可能会觉得奇怪:改变任务状态的resolve和reject方法是定义在deferred对象上,但最后返回的却是deferred的promise属性。这么做一是因为规范就是这么定的,二是可以防止任务状态被外部改变。

then有三个参数,分别是onFulfilled、onRejected、onProgress,通过这三个参数,就可以指定上一个任务在resolve、reject和notify时该如何处理。例如上一个任务被resolve(data),onFulfilled函数就会被触发,data作为它的参数;被reject(reason),那么onRejected就会被触发,收到reason。任何时候,onFulfilled和onRejected都只有其一可以被触发,并且只触发一次;onProgress顾名思义,每次notify时都会被调用。下面是reject和notify的用法(在浏览器中预览):

JSfunction run() {
var deferred = when.defer();
var start = 1, end = 100; (function() {
if(start <= end) {
deferred.notify(start++);
setTimeout(arguments.callee, 50);
} else {
deferred.reject('time out!');
}
})(); return deferred.promise;
} run().then(undefined,
function(reason) {
alert(reason);
}, function(s) {
document.getElementById('output').innerHTML = s + '%';
}
);

then会传递错误,也就是说有多个任务串行执行时,我们可以只在最后一个then定义onRejected。只定义了onRejected的then等同于otherwise,也就是说 otherwise(onRejected) 是 then(undefined, onRejected) 的简便写法。

then会在try..catch..的包裹之下执行任务,所以任务的异常都会被when.js捕获,当做失败状态处理,类似这样:

JStry {
...
} catch (e) {
deferred.reject(e);
}

在任务状态改变之后再then,依然可以正常工作,后续任务会立刻执行。如果要在多个任务最后做cleanup工作,而不管之前的任务成功与否,可以用ensure方法。它只接受一个参数onFulfilledOrRejected,始终会执行。另外when.js还有一个always方法,即将废弃,建议大家不要使用。

回到上面加载图片的场景,如果把任务2变为:加载多张图片,全部完成后再执行任务3。这时候需要用到when.all,when.all接受一个promise数组,返回promise,这个promise会在promise数组中每一个promise都resolve之后再resolve。说起来拗口,看代码就明白了(在浏览器中预览):

JSvar getData = function() {
var deferred = when.defer(); $.getJSON(api, function(data){
var data = data.slice(0, 3);
deferred.resolve(data);
}); return deferred.promise;
} var getImg = function(src) {
var deferred = when.defer(); var img = new Image(); img.onload = function() {
deferred.resolve(img);
}; img.src = src; return deferred.promise;
} var showImgs = function(imgs) {
$(imgs).appendTo($('#container'));
} var getImgs = function(data) {
var deferreds = [];
for(var i = 0; i < data.length; i++) {
deferreds.push(getImg(data[i]));
}
return deferreds;
} when.all(getData().then(getImgs)).then(showImgs);

如果我们只是想把一个promise数组挨个执行一遍,可以用when.settle:

JSvar promise1 = function() {
var deferred = when.defer();
setTimeout(function() {
deferred.reject('A');
}, 2000);
return deferred.promise;
}; var promise2 = function() {
var deferred = when.defer();
setTimeout(function() {
deferred.resolve('B');
}, 2000);
return deferred.promise;
}; when.settle([promise1(), promise2()]).then(function(result) {
console.log(result); /*
[{"state":"rejected","reason":"A"},
{"state":"fulfilled","value":"B"}] */
});

有时候,我们需要引入任务竞争机制,例如从一批cdn中找到最快的那个,when.any就派上用场了,when.any接受promise数组,在其中任何一个resolve后就接着执行后续任务了。如果要在一批promise中某几个resolve后执行后续任务,可以用when.some,它比when.any多一个howMany的参数。

Promise给异步编程代码带来了巨大的方便,从此我们可以更专注单个任务的实现,promise会很好的替我们解决任务调度问题。when.js提供的功能远远不止本文提到的这些,有兴趣的同学可以前往官方api文档了解更多。

异步编程:When.js快速上手的更多相关文章

  1. 异步编程when.js

    when.js很小,压缩后只有数kb,gzip后的大小几乎可以忽略.在Node和浏览器环境里都可以使用when.js 首先,我们看一小段代码: var getData = function(callb ...

  2. JS快速上手-基础Javascript

    1.1背景 1.1.1 ECMAScript与javascript ECMAScript是javascript的官方命名.因为java已经是一个商标.如今,一些早前收到过授权的公司,如Moailla, ...

  3. Three.js 快速上手以及在 React 中运用[转]

    https://juejin.im/post/5ca22692f265da30a53d6656 github 的地址 欢迎 star! 之前项目中用到了 3D 模型演示的问题,整理了一下之前学习总结以 ...

  4. Vue.JS快速上手(组件生命周期)

    一.什么是组件 组成网页独立功能基本单元(片段), 复用.维护.性能, Vue.js中的组件就是一个Vue的实例,Vue中的组件包含data/methods/computed. 一个Vue.js的应用 ...

  5. Vue.JS快速上手(组件间的通信)

    前言 Vue采用的是组件化思想,那么这些组件间是如何通信的呢?下面详细介绍一下. 所谓组件间通信,不单单是我们字面上理解的相互传递数据,这里还包括一个组件访问另一个组件的实例方法等,如父组件通过ref ...

  6. Vue.JS快速上手(指令和实例方法)

    1.声明式渲染 首先,我们要知道Vue是声明式渲染,那啥是声明式渲染,我们只需要告诉程序我们想要什么结果,其他的交给程序来做.与声明式渲染相对的是命令式渲染,即命令我们的程序去做什么,程序就会跟着你的 ...

  7. Vue.JS快速上手(Vue-router 实现SPA 开发)

    一.什么是路由 URL -> 映射 -> 组件 Hash+onhashchange History.pushstate+replaceState+onpopstate 二.准备工作 组件 ...

  8. 【异步编程】when.js

    异步编程:When.js快速上手 var api = 'http://qgy18.imququ.com/file/when/d.php?cb=?'; var getData = function() ...

  9. ES6 --- JS异步编程的几种解决方法及其优缺点

    导言: 我们都知道 JS 是单线程的,这也正是异步编程对于 JS  很重要的原因,因为它无法忍受耗时太长的操作.正因如此有一系列的实现异步的方法. 方法一  setTimeout 常用于:定时器,动画 ...

随机推荐

  1. bsp STEP

    Web开发不仅现在比较流行,将来也会.我来谈一下最近bsp  application项目的体会吧,属初学者,请各位多多指教. SAP 的web开发方法有很多种,bsp只是其中一种,而bsp开发有可以分 ...

  2. html页面的head标签下

    head区是指首页html代码的<head>和</head>之间的内容.  必须加入的标签  1.公司版权注释  <!--- the site is designed b ...

  3. Bubble Sort_树状数组

    Problem Description P is a permutation of the integers from 1 to N(index starting from 1).Here is th ...

  4. [转]-Lua协程的实现

    协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制:另 外,协程也更加轻量级.这样,在遇到某些可能阻塞的操作时,可以使 ...

  5. Python12期培训班-day1-三级菜单代码分享

    #!/usr/bin/env python3 import sys import os zonecode = { '广东省': {'广州市':['越秀区','海珠区','荔湾区','天河区'], '深 ...

  6. 在ubuntu下安装QQ

    (参考链接   :http://jingyan.baidu.com/album/47a29f24577776c01423991a.html?picindex=3) 一 .安装  wine 1.下载一个 ...

  7. Codeforces Round #156 (Div. 2)

    A. Greg's Workout 模3求和,算最大值. B. Code Parsing 最后左半部分为x,右半部分为y,那么从中间不断去掉xy,直到其中一种全部消去. C. Almost Arith ...

  8. MSP430学习笔记:UART

    串通可以两种方法其现 一.USART硬件直接实现 二.通过定时器软件实现 该模块可现现: UART异步串行通讯 SPI同步串行通讯 I2C同步串行通讯 UxCTL控制寄存器 7:PENA 6:PEV ...

  9. 【转载】解决方案:Resharper对系统关键字提示‘can not resolve symbol XXX’,并且显示红色,但是编译没有问题

    环境:Visual studio 2013 community Update 4 + Resharper 8.2 + Windows 7 现象: 我的C#工程编译没有问题, 但是在代码编辑器中系统关键 ...

  10. Ubuntu14.04搭建Caffe(仅CPU)

    一直以来都没有写博客的习惯,后来发现以前做的工作如果不注意及时整理和记录往往丢失的很快.对我而言这是一篇具有重要意义的文章,好的习惯要持之以恒,以后的日子我会常驻博客园!由于本人水平有限,智商略低,欢 ...