随着
JS
在服务端的发展(例如Node
),在处理数据库事务,操作文件等需要异步操作的地方,回调函数的缺点随着异步操作的复杂越来越明显,使用回调函数也越来越难以满足开发需要。为了解决回调函数的种种缺点,使包含异步操作的代码更简洁和具有可读性,ES6提出了
Promise
的实现。
回调函数
为了不在进行表单检查,操作DOM
等情况时阻塞页面,JS
包含了大量的异步操作,随之诞生的就是回调函数。
回调函数是JavaScript
中约定的一个俗称,指那些需要异步操作完成后才会进行调用的函数。主要用在那些需要花费时间,但又不能一直阻塞代码执行的情况,例如Ajax
和 File
操作。
我们常见的jQuery
库中的ajax
相关API就是回调函数应用的一个比较显著的例子
1 | $.get('ajax/test.html',function(data){ |
但是回调函数也存在以下的一些缺点:
- 著名的 callback hell 回调地狱,在进行多个顺序依赖的异步操作时,很多个回调函数,一层层嵌套,导致代码可读性极低,不利于阅读和维护。
- 调用函数和回调函数并不会在同一个堆栈中运行,这会导致我们无法对回调函数内部发生的异常进行
try-catch
和准确定位,无法使用throw
抛出异常,无法使用return
终止函数的调用等问题。 - 回调函数使用了
JS
闭包,在比较复杂的项目环境时,容易出现变量污染等难以定位和调试的问题
正是由于回调函数的这些缺点,Promise
应运而生。
promise
的创建和使用
在ES6
中被实现的Promise
,主要用途就是进行异步计算,通过将异步操作队列化,使其按我们期望的顺序执行,并返回我们期望的结果。
创建promise
对象
我们可以通过Promise
这个原生构造函数来创建一个promise
。
1 | var promise = new Promise(function(resolve,reject){ |
构造函数Promise
接受一个函数作为参数,这个函数有两个参数resolve
和reject
(可省略),这两个参数都是Promise
对象的方法。
使用new Promise()
创建的promise
对象有三个状态:
- Pending
promise
对象刚被创建后的初始状态 - Fullfilled 异步操作成功状态,可使用
resolve
方法将promise
置为此状态并将异步操作的结果传递出去 - Rejected 异步操作失败状态,可使用
reject
方法将promise
置为此状态并将异步操作的错误传递出去
promise
的状态改变是不可逆的,也就是说,从初始化状态 Pending 转换为 Fullfilled 或 Rejected 后,promise
的状态就不会再改变了。
promise.then()
在Promise
对象的的创建中我们提到通过resolve
方法传递结果和通过reject
方法传递错误,那想要接收这些结果和错误,就需要用到promise
对象的then()
方法了:
1 | promise.then(onFulfilled,onRejected) |
.then
接受两个函数作为参数。这两个函数参数都是可选的。
onFulfilled
在promise
对象通过resolve
将状态置为Fulfilled
时会被调用,接受resolve
传递过来的结果作为函数参数。onRejected
在promise
对象通过reject
将状态置为Rejected
时会被调用,接受reject
传递过来的错误作为函数参数。
.then
的参数函数的显式返回值会被包装为一个同样具有.then
方法的新的promise
对象,下一个.then
的onFulfilled
和onRejected
针对这个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 | function getUrl(url){ |
小结
通过以上的介绍和代码,我们就知道了promise
的基本使用方法:
- 使用
new Promise
创建promise
对象,使用resolve
和reject
传递异步结果。 - 使用
.then
或.catch
对promise
对象传递出的结果进行处理。
Promise
的静态方法
Promise
提供了几个静态方法,用于辅助我们使用Promise
。
Promise.resolve()
Promise.resolve()
也是用于创建一个promise
对象,可以认为是如下形式的new Promise()
:
1 | Promise.resolve('200') |
对于Promise.resolve()
,它总是返回一个promise
对象,并且会将promise
对象立刻置为resolved
状态(除非解析发生错误或传入了状态为rejected
的promise
对象),进而触发后续then()
中的onFulfilled
函数。
有以下几点需要注意:
- 如果传递给
Promise.resolve()
的参数为一个直接的值,它会把它包装为promise
对象返回,下一个.then
的onfulfilled
函数会立即获得这个值,但仍然是异步的。 - 如果传递给
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 | Promise.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 | var arr = [1, 2, 3]; |
需要注意的是,
Promise.all()
参数数组中所有promise
对象包裹的异步操作都是并发执行的,他们的结果互不干扰互不依赖。如果想实现队列型的异步操作,应该使用链式调用
.then()
的方式来实现。
Promise.race()
Promise.race()
和 Promise.all()
一样,也是接受一个promise
对象的数组,将数组中的所有promise
对象包装为一个新的promise
对象。
但不同的是,这个新promise
对象的状态由数组中率先发生状态变化的promise
对象来决定(race 也就是赛跑的意思)。当数组中第一个发生状态变化的对象转换为resolved
时,它也会转换为resolved
状态。当数组中第一个发生状态变化的对象转换为rejected
状态时,它也会转换为rejected
状态。
1 | var p1 = new Promise((resolve)=>{ |
需要注意的是,
Promise.race
在第一个promise对象改变状态之后,是不会去取消其他promise对象的执行的。
总结
以上就是关于ES6
中Promise
的一些知识,在看完这篇文章之后,大家应该对Promise
有了比较清晰的了解和认识。
但本人才疏学浅,错误和遗漏在所难免。如果想要进一步深入的学习Promise
,为大家提供以下两个文章链接,供大家继续深入的了解和学习promise
的相关知识: