Fork me on GitHub

ES6之Promise

随着JS在服务端的发展(例如Node),在处理数据库事务,操作文件等需要异步操作的地方,回调函数的缺点随着异步操作的复杂越来越明显,使用回调函数也越来越难以满足开发需要。

为了解决回调函数的种种缺点,使包含异步操作的代码更简洁和具有可读性,ES6提出了Promise的实现。

回调函数

为了不在进行表单检查,操作DOM等情况时阻塞页面,JS包含了大量的异步操作,随之诞生的就是回调函数。

回调函数是JavaScript中约定的一个俗称,指那些需要异步操作完成后才会进行调用的函数。主要用在那些需要花费时间,但又不能一直阻塞代码执行的情况,例如AjaxFile操作。

我们常见的jQuery库中的ajax相关API就是回调函数应用的一个比较显著的例子

1
2
3
4
5
$.get('ajax/test.html',function(data){
var somehtml =data;
})
//在上面代码中,ajax请求就是一个需要耗费时间的操作。
//我们通过回调函数,来在这个操作完成后,进行相应的处理,却不阻塞代码的继续运行。

但是回调函数也存在以下的一些缺点:

  • 著名的 callback hell 回调地狱,在进行多个顺序依赖的异步操作时,很多个回调函数,一层层嵌套,导致代码可读性极低,不利于阅读和维护。
  • 调用函数和回调函数并不会在同一个堆栈中运行,这会导致我们无法对回调函数内部发生的异常进行try-catch和准确定位,无法使用throw抛出异常,无法使用return终止函数的调用等问题。
  • 回调函数使用了JS闭包,在比较复杂的项目环境时,容易出现变量污染等难以定位和调试的问题

正是由于回调函数的这些缺点,Promise应运而生。

promise的创建和使用

ES6中被实现的Promise,主要用途就是进行异步计算,通过将异步操作队列化,使其按我们期望的顺序执行,并返回我们期望的结果。

创建promise对象

我们可以通过Promise这个原生构造函数来创建一个promise

1
2
3
4
5
6
7
8
9
10
var promise = new Promise(function(resolve,reject){
//....异步操作
if(/*success*/){
//...执行代码
resolve(result)
} else {
//...执行代码
reject(error)
}
})

构造函数Promise接受一个函数作为参数,这个函数有两个参数resolvereject(可省略),这两个参数都是Promise对象的方法。

使用new Promise() 创建的promise对象有三个状态:

  • Pending promise对象刚被创建后的初始状态
  • Fullfilled 异步操作成功状态,可使用resolve方法将promise置为此状态并将异步操作的结果传递出去
  • Rejected 异步操作失败状态,可使用reject方法将promise置为此状态并将异步操作的错误传递出去

promise的状态改变是不可逆的,也就是说,从初始化状态 Pending 转换为 FullfilledRejected 后,promise的状态就不会再改变了。

promise.then()

Promise对象的的创建中我们提到通过resolve方法传递结果和通过reject方法传递错误,那想要接收这些结果和错误,就需要用到promise对象的then()方法了:

1
2
3
4
5
promise.then(onFulfilled,onRejected)

//两个参数均为可选参数,可选择只传入任意一个
promise.then(onFulfilled)
promise.then(undefine,onRejected)

.then接受两个函数作为参数。这两个函数参数都是可选的。

  • onFulfilledpromise对象通过resolve将状态置为Fulfilled时会被调用,接受resolve传递过来的结果作为函数参数。
  • onRejectedpromise对象通过reject将状态置为Rejected时会被调用,接受reject传递过来的错误作为函数参数。

.then的参数函数的显式返回值会被包装为一个同样具有.then方法的新的promise对象,下一个.thenonFulfilledonRejected针对这个promise对象遵循相同的调用方式,从而实现.then的链式调用。

使用.then 来进行异常处理时,可使用promise.then(undefine,onRejected),只指定reject时的处理函数。

但其实更好的办法是使用promise.catch(onRejected) 这个专门用于处理异常的promise方法。它的优势在于在链式调用.then时,它可以捕捉到前面所有then中传递出的异常。

但要注意的是,除非.then().catch()内部抛出异常或使用了rejecte()将状态置为rejected,他们都会返回Fulfilled态的promise对象。

一个Promise的基本例子

我们通过使用promise来实现一个类似jQuery$.get()的函数,从而对以上介绍能有更直观的认识:

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
function getUrl(url){
return new Promise(function(resolve,reject){
let req = new XMLHttpRequest();
req.open('GET',url,true);
req.onload = ()=>{
if(req.status === 200){
//当请求成功时调用 resolve来将结果传递出去
resolve(req.responseText)
} else{
//当请求结果跟预期不符时使用 reject 传递一个错误
reject(new Error(req.statusText))
}
};
//当请求发生错误时同样使用 reject 传递一个错误
req.onerror = ()=>{
reject(new Error(req.statusText))
};
req.send();
})
}

var URL = "http://someaddress.com/get"
//使用 .then 来接受包裹了异步操作的 promise 对象所传递出的结果
getUrl(URL).then(function onFulfilled(resolve_value){
//为了方便理解我们把函数命名为 onFulfilled
//请求成功时传递的结果
console.log(resolve_value);
}).catch(function onRejected(reject_error){
//为了方便理解我们把函数命名为 onRejected
//请求失败时传递的错误
console.error(reject_error)
})

小结

通过以上的介绍和代码,我们就知道了promise的基本使用方法:

  • 使用 new Promise 创建promise对象,使用resolvereject传递异步结果。
  • 使用 .then.catchpromise对象传递出的结果进行处理。

Promise的静态方法

Promise 提供了几个静态方法,用于辅助我们使用Promise

Promise.resolve()

Promise.resolve()也是用于创建一个promise对象,可以认为是如下形式的new Promise():

1
2
3
4
5
Promise.resolve('200')
//相当于
new Promise(function(resolve){
resolve('200')
})

对于Promise.resolve(),它总是返回一个promise对象,并且会将promise对象立刻置为resolved状态(除非解析发生错误或传入了状态为rejectedpromise对象),进而触发后续then()中的onFulfilled函数。

有以下几点需要注意:

  • 如果传递给Promise.resolve()的参数为一个直接的值,它会把它包装为promise对象返回,下一个.thenonfulfilled函数会立即获得这个值,但仍然是异步的
  • 如果传递给 Promise.resolve() 的参数为一个promise对象,它不会做任何处理,而是直接返回传入的promise对象。
  • 如果传递给Promise.resolve()的参数为一个具有.then方法的对象,它会将其包装为一个promise对象返回,并立即执行它的then方法。
  • Promise.then()中的 onFulfilled函数的显式返回值,即是通过Promise.resolve()包装为promise对象供后续的then使用的。

Promise.reject()

Promise.resolve()类似,Promise.rejct()也是用于创建一个promise对象,可以认为是如下形式的new Promise():

1
2
3
4
5
Promise.reject(new Error('错误'))
//相当于
new Promise(function(null,reject){
reject(new Error('错误'))
})

对于Promise.reject(),它总是返回一个promise对象,并且会将这个promise对象立刻置为rejected状态,进而触发后续then()中的onRejected函数。

Promise.all()

Promise.all()接受一个promise对象的数组作为参数,它会将数组中所有promise对象实例包装为一个新的promise对象。

这个新promise对象的状态由数组中所有promise对象的状态来决定。当数组中所有对象都resolved时,它也会转换为resolved状态。当数组中有一个对象转换为rejected状态时,它就会转换为rejected状态。

这个新promise对象的结果为传入数组中各个promise的结果组成的一个数组。

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3];
var promise_arr = arr.map(function(x){
return new Promise(function(resolve, reject){
resolve(5*x);
});
});

Promise.all(promise_arr).then(function(result){
console.log(result); //[5,10,15]
})

需要注意的是,Promise.all() 参数数组中所有promise对象包裹的异步操作都是并发执行的,他们的结果互不干扰互不依赖。

如果想实现队列型的异步操作,应该使用链式调用.then()的方式来实现。

Promise.race()

Promise.race()Promise.all()一样,也是接受一个promise对象的数组,将数组中的所有promise对象包装为一个新的promise对象。

但不同的是,这个新promise对象的状态由数组中率先发生状态变化的promise对象来决定(race 也就是赛跑的意思)。当数组中第一个发生状态变化的对象转换为resolved时,它也会转换为resolved状态。当数组中第一个发生状态变化的对象转换为rejected状态时,它也会转换为rejected状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var p1 = new Promise((resolve)=>{
setTimeout(resolve,300,'p1 finish');
})

var p1 = new Promise((resolve)=>{
setTimeout(resolve,100,'p2 finish');
})

var p1 = new Promise((resolve,reject)=>{
setTimeout(reject,200,'p3 finish');
})

Promise.race([p1,p2,p3]).then((result)=>{
//p2 最快(很明显)
console.log(result) //p2 finshed
}).catch((err)=>{
console.log(err); //并不会执行
})

需要注意的是, Promise.race在第一个promise对象改变状态之后,是不会去取消其他promise对象的执行的。

总结

以上就是关于ES6Promise的一些知识,在看完这篇文章之后,大家应该对Promise有了比较清晰的了解和认识。

但本人才疏学浅,错误和遗漏在所难免。如果想要进一步深入的学习Promise,为大家提供以下两个文章链接,供大家继续深入的了解和学习promise的相关知识:

----本文结束感谢阅读----