ES6学习笔记之Promise

  • Learning Card

    • 同步VS异步
      • JS是单线程语言
      • 主线程处理同步,异步操作在满足触发条件时加入到任务队列中
      • Promise用来处理异步操作
    • Promise是什么
      • 一个容器,里面包含未来才会结束的事件
    • 用法
      • Promise构造函数
      • 包含resolvereject两个函数
    • 方法
      • Promise.prototype.then()
      • Promise.all()
      • Promis.race()

同步 VS 异步

JS是单线程的语言,它的主线程用来执行代码中的同步任务,无法立即执行的异步任务会由不同的线程来处理,如定时器触发线程,异步http请求线程,但最终,这些任务在满足触发条件之后,都会被放入由事件触发线程管理的任务队列中。在主线程空闲时来处理任务队列中的任务。

Promise就是来处理异步操作的,即处理最终什么样的任务会被添加到任务队列中去。

Promise是什么

  • 解决异步的一种方法
  • 本质:一个容器,里面包含着某个未来才会结束的事件
  • 特点:
    1. 对象的状态不受外界影响
      • 三种状态:pending, fulfilled, rejected
      • 只有异步操作的结果可以决定当前是哪一种状态
      • 我们可以这样理解,一个Promise对象包含了一个处于pending状态的任务,这个任务根据自身的内容可以被归类到不同的线程中,我们通过在Promise内部的代码进行判断,决定任务的结果是成功fulfilled还是失败rejected,无论是成功还是失败,它们都有对应的回调函数,Promise解决的就是在判断之后将对应的回调函数加入到任务队列中去
    2. 一旦状态改变,就不会再变
      • pending => fulfilled
      • pending => rejected
  • 缺点:
    1. 无法取消
    2. 必须设置回调函数,否则抛出错误
    3. 处于pending状态时,无法得知目前进展到哪一个阶段

基本用法

1
2
3
4
5
6
7
8
const promise = new Promise(function(resolve, reject) {
//....
if(/*成功*/) {
resolve(value);
} else {
reject(error);
}
})

Promise对象有两个参数,resolve表示成功,reject表示失败,它们都接受一个返回值,resolve接收到的返回值是任务反馈回来的数据valuereject接收到的是error错误本身。

但是这只定义了promise对象,并没有将回调函数加入到任务队列中去,执行完成promise还需要用后面的方法。

方法

Promise.prototype.then()

Promise.prototype.then()方法就是用来添加状态改变时的回调函数。它同样有两个参数,一个是resolve成功时的回调函数,一个是reject失败时的回调函数。相当于Promise构造函数部分决定了什么条件下算成功,什么条件下算失败,.then()方法明确了成功和失败各自情况下会有什么操作,非常明了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject) {
const handler = function() {
if(this.readyState !== 4) {
return;
}
if(this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
}
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
})
return promise;
}

以上是一段AJAX请求的代码,里面将返回的内容封装到一个Promise对象中,请求成功时得到的是this.response数据,失败时使用的是this.statusText错误状态码来作为回调函数的值。接下来我们可以用.then()方法来处理这个promise对象

1
2
3
4
5
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json)
}, function(error) {
console.error('出错了' ,error)
});

需要注意的是,Promise.prototype.then()返回的是一个新的Promise对象,因此在.then()方法后面可以再添加新的.then()方法来处理新的数据,使用链式then

如果我们需要连续地请求一连串的数据时,Promise方法会使得这些请求写起来非常地简单,有种使用同步的写法来实现异步操作的感觉。

Promise.all()

Promise.all()方法用于将多个Promise实例包装成一个新的Promise实例。

1
const p = Promise.all([p1, p2, p3]);

还是借用上面的getJSON的例子,如果我们要读取”/posts/1.json”,”/posts/2.json”,”/posts/3.json”,”/posts/4.json”…,我们可以这样做

1
2
3
4
5
6
7
8
9
10
11
getJSON("/posts/1.json").then(function(json) {
console.log('Contents: ' + json)
}, function(error) {
console.error('出错了' ,error)
});
getJSON("/posts/2.json").then(function(json) {
console.log('Contents: ' + json)
}, function(error) {
console.error('出错了' ,error)
});
...

但是这样明显会麻烦很多,我们也可以用简单一些的方法

1
2
3
4
5
6
7
const promises = [1, 2, 3, 4, 5, 6].map(function(id) {
return getJSON('/posts/' + id + '.json');
});
Promise.all(promises).then(function(posts) {
let [res1, res2, res3, res4, res5, res6] = posts;
//...
})

我们可以通过Promise.all()来把所有的promises解析出来,它接受一个数组作为参数,也返回一个包含了各个Promise返回的数据的数组作为resolve回调函数的参数,这时,我们可以通过解构赋值来将每个Promise对应的数据解析出来,从而可以单独操作每个Promise返回的数据。

此外,jQuery的高级版本中是包含了Promise对象的,也就是说,我们在使用比如ajax请求的时候,可以直接使用Promise.all方法,而不用先构造Promise对象。

1
2
3
4
5
6
7
8
9
Promise.all([
$ajax({url: 'data/arr.txt', dataType:'json'}),
$ajax({url: 'data/json.txt', dataType:'json'})
]).then(function(results) {
let [arr, json] = results;
console.log(arr, json);
}, function(err) {
console.log(err);
});

Promise.race()

Promise.race()同样是将多个Promise的实例包装秤一个新的Promise的实例,不过它是根据参数数组中最先改变状态的Promise来改变返回值。


ES6学习笔记的内容主要基于阮一峰老师的《ECMAScript 6入门》,原文请见链接