Contents
  1. 1. 遇到问题
  2. 2. 解决方案
  3. 3. Promise
    1. 3.1. 伪代码
    2. 3.2. 代码分析
    3. 3.3. 进阶
  4. 4. 参考&致谢

做了一段时间的WEB前后台开发,个人感觉,对于前端页面,异步操作带给用户的良好体验,真的挺重要的,页面不会卡在那里,也不需要重新加载,体验真的棒棒哒。所以,对于Ajax的使用就越来越多,也越来越重要。
那有的时候,我们就会遇到一个操作需要等待另一个操作返回之后再决定如何操作的情况,就是一个Ajax需要依赖于前一个Ajax操作;但是JS又是单线程的,这个时候,Promise就登场了。这篇blog就详细来讲解下如何使用Promise完成异步操作的。

遇到问题

最近项目中遇到一个问题,用户可以通过某个页面新建了一个对象(比如一个公司);同时,页面中可以选择填写与这个将要新建对象的一些相关属性(比如公司默认的管理员用户);那对于后端来说,需要先在数据库中生成一条公司的记录,而其他属性可能是存在另外一张表(用户表)中,当我们得知该条公司生成成功之后(拿到ID),再根据ID生成和其对应的其他信息。

前端页面中,用户填写的这2个表单(1是公司,2是用户)是同属一个页面的,那前端用户当然不希望点击2次提交,他只需要点击一次,我们就完成了这两个操作。
这个过程当然可以在一个ajax操作中完成,我们把所有的信息都写入参数中,然后两条数据库语句分别完成对公司表和用户表的写入,并返回结果。

但是显然这样做的不足之处是:不易扩展,程序耦合性太高。
假如前端用户对于创建公司管理员的操作是可选的,那参数就是变化的个数了;
再假如我们除了人员信息,还需要再写入另外的信息呢?ajax的请求如果把这些参数全包含进来会显得异常臃肿。

解决方案

一个可见的解决方案,就是把公司的新建过程,和用户的新建过程,分别放到2个Ajax请求中。

  1. 发起第1个Ajax:新建公司
  2. 新建公司成功,返回新建立的公司id,发起第2个Ajax,新建员工;当新建公司失败,直接提示用户失败;
  3. 第2个Ajax返回成功,提示用户成功;第2个Ajax返回失败,提示用户新建公司成功但新建用户失败。

所以,我们需要一种可以支持异步调用JS的方法,虽然是单线程,但是当第1个Ajax返回之后,通过回调函数来执行第2个Ajax;

Promise

这个时候就需要我们的Promise用法登场了。我们不妨先来看一看对于上述问题,Promise的伪代码是如何实现的:

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* **************************************************************************** */
// 新建promise对象用于操作第1个Ajax
var promise = new Promise(function(resolve,reject) {
$.ajax({
url : '新建公司的url',
type : 'POST',
data : {新建公司需要用到的参数},
dataType: 'JSON',
success: function (json) {
// 这里调用成功的回调函数,把返回的信息(json.info)传过去
resolve(json.info);
},
error: function (textStatus, errorThrown) {
// 可以直接提示用户失败
// 为了演示promise的完整方法,这里调用失败的回调函数reject
reject();
}
});
});
/* **************************************************************************** */
// 配置promise之后的回调函数,成功回调中操作第2个Ajax
// 成功的回调
promise.then(
function(com_id) {
$.ajax({
url : '新建用户的url',
type : 'POST',
data : {新建用户的各项参数,其中包括我们上一步创建成功的com_id},
dataType: 'JSON',
success: function (json) {
// 第2步成功返回
console.log("新建公司和员工都成功");
},
error: function (textStatus, errorThrown) {
// 第2步失败
console.log("新建公司成功但新建员工失败");
}
});
},
// 失败的回调
function() {
console.log("新建公司失败");
}
);

代码分析

上面的代码是我经过整理以后,以最简单和最容易理解的方式写了出来。
整个代码分为2大部分,第1部分是新建一个promise对象;第2部分是完成“流式”的异步操作。
我们先来看第一部分,promise对象。
promise对象内部是一个接收2个参数的匿名函数,这两个参数分别是promise执行后 成功和失败的回调函数。
在匿名函数内部我们可以看到,就是一个很简单的jQuery的ajax请求,当请求成功后,执行成功的回调:resolve; 当请求失败后,执行失败的回调: reject

接下来我们来看第2部分代码,就是异步的部分。

注意:虽然JS是单线程的,但是它们不都是同步的,也可以实现异步;也就是说:单线程同步多线程异步;如果不清楚这其中的关系,自行google。

第2部分代码其实就是完成promise对象的两个回调,用then方法来实现。 then方法接收两个函数,第一个就是成功的回调resolve,第2个参数则是失败时的回调reject

1
promise.then(function(){}, function(){});

至于回调函数的部分,就和我们常用的JS代码没有任何区别了,这里就不做过多的解释了。

提示:

  • then方法的第2个参数是可以省略的,如果你之前的promise对象不处理失败后的情况(我在注释中也写了),仅有成功的回调,可以写成promise.then(function(){});

进阶

我之前用到了一个词:流式的异步操作。 虽然听上去是矛盾的,既然是异步操作,怎么可能还是流式的,流式就是针对同步操作来说的呀。
那是因为,我们可以通过then方法,来流式地调用回调。怎么理解呢?
还是拿上面那个例子来说,假如我们新建用户之后,继续有一系列的ajax请求,这些请求都是依赖于前一个的,我们就可以通过类似下面形式的代码来实现:

1
2
3
4
5
6
7
8
9
10
promise.then(
// 第1组ajax操作返回后执行的回调
function(){}, function(){}
).then(
// 第2组ajax操作返回后执行的回调
function(){}, function(){}
).then(
// 第3组ajax操作返回后执行的回调
function(){}, function(){}
);

虽然是很多个异步操作,但是从代码上看,它们仿佛又是被"同步"调用的一样,所以,流式的异步操作就是说看起来像是同步,其实是异步操作。
只不过,因为then方法是要promise对象才能调用的,所以在每组回调函数中,均需要new一个promise对象,然后返回这个new出来的对象就好了。

参考&致谢

JS的异步操作还有很多其他方法,但总体来说,我觉得Promise方法是最适合的,因为它从代码上来说非常容易理解;可能有很多人觉得写起来会有点困难,但是看过我的伪代码之后,其实也没有那么难,是吧?

感谢:

Contents
  1. 1. 遇到问题
  2. 2. 解决方案
  3. 3. Promise
    1. 3.1. 伪代码
    2. 3.2. 代码分析
    3. 3.3. 进阶
  4. 4. 参考&致谢