爱客仕-前端团队博客园

浅谈Promise原理及方法

之前分享的promise顺便整理出来写成博客,真是一举两得
我是真不太会说,何况是技术性的,临时上阵真的是硬着头皮,还是看我写的吧。
我是先接触到angular中的$q后才知道es6中有promise这个玩意,发现他完全解决了之前的回调再回调的噩梦,大赞!

Promise的引入

先看一段原始代码,为了获取上一个函数的返回结果只能通过回调的方式,若一层层的就出现了下面的代码:

1
2
3
4
5
6
7
step1(function(value1){
step2(function(value2){
step3(function(value3){
// 如此层层嵌套
})
})
})

  • 代码不优雅,阅读吃力
  • 不利于维护
  • 还要担心某一层未获取到数据的问题

由此可见,没有看到利而发现了N多弊端,忍受了那么久终于在es6中引入了promise对象,看一下它的写法:

1
2
3
4
(new Promise(step1))
.then(step2)
.then(step3)
// 一直这么美下去

promise以同步的方式来操作异步,不用担心错过某个事件或信号,它是等上一个异步操作有结果了再执行它所寄存的下一个操作,大方美丽有远见…..
下面说一下它的几个重要的点:

  1. promise有三种状态 等待(pending)、已完成(fulfilled)、已拒绝(rejected)
  2. promise的状态只能从“等待”转到“完成”或者“拒绝”,不能逆向转换,同时“完成”和“拒绝”也不能相互转换
  3. Promise必须有一个then方法,并且要返回一个promise,供then的链式调用
  4. then接受两个回调(成功与拒绝),在相应的状态转变时触发,回调可返回promise,等待此promise被resolved后,继续触发then链

Promise的基本用法

ES6的Promise对象是一个构造函数,用来生成Promise实例。下面是Promise对象的基本用法:

1
2
3
4
5
6
7
8
9
var promise = new Promise((resolve, reject) => {
if (/* 异步操作成功 */) {
resolve(value)
} else {
reject(error)
}
})
promise.then(onFulfilled,onRejected).then(onFulfilled,onRejected)

这是我们项目中常用的方法,大家并不陌生。但还是说一下大概原理,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resovle和reject方法。异步操作成功时,则用resolve方法将Promise对象的状态改为“成功”;异步操作失败时,则用reject方法将状态改为“失败”。
promise实例生成以后,then传入回调,这里 的函数并不会立即执行而是加入队列,等待未来resolve或reject时触发执行。
上述解释的大概意思的源码是这样的,这里就写个then方法,:

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
45
46
47
48
49
50
51
52
53
54
55
56
var Promise = function(resolver){
if (!isFunction(resolver))
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
if(!(this instanceof Promise)) return new Promise(resolver);
var promise = this;
promise._value;
promise._reason;
promise._status = PENDING;
promise._resolves = [];
promise._rejects = [];
var resolve = function(value){
//状态转换为FULFILLED
//执行then时保存到_resolves里的回调,
//如果回调有返回值,更新当前_value
}
var reject = function(reason){
//状态转换为REJECTED
//执行then时保存到_rejects里的回调,
//如果回调有返回值,更新当前_rejects
}
resolver(resolve,reject);
}
Promise.prototype.then = function(onFulfilled,onRejected){
var promise = this;
// 每次返回一个promise,保证是可thenable的
return Promise(function(resolve,reject){
function callback(value){
var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
if(isThenable(ret)){
// 根据返回的promise执行的结果,触发下一个promise相应的状态
ret.then(function(value){
resolve(value);
},function(reason){
reject(reason);
});
}else{
resolve(ret);
}
}
function errback(reason){
reason = isFunction(onRejected) && onRejected(reason) || reason;
reject(reason);
}
if(promise._status === PENDING){ // 状态为“等待”时,将回调函数加入队列
promise._resolves.push(callback);
promise._rejects.push(errback);
}else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行
callback(promise._value);
}else if(promise._status === REJECTED){
errback(promise._reason);
}
});
}

完整代码参考:https://github.com/ygm125/promise/blob/master/promise.js

Promise.prototype.then()

1
2
3
4
5
6
7
8
9
function getPromise(n) {
return new Promise((resovle, reject) => {
if(n <= 100) {
resovle(n)
} else {
reject('请传入100以内的数值!')
}
})
}

首先我先实例化一个Promise,之后介绍的方法都调用此函数作为例子

1
2
3
4
5
6
getPromise(1).then((a) => {
return a + 1
}).then((b) => {
console.log(b)
})
// 结果:2

then方法依次指定了两个函数,第一个回调函数返回的值传递给下一个函数作为参数。这种设计使得嵌套的异步操作得以改写。

Promise.prototype.catch()

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定发生错误时的回调函数。

1
2
3
4
5
6
getPromise(101).then((value) => {
console.log(value)
}).catch((error) => {
console.log(error)
})
// 结果:请传入100以内的数值!

Promise.all()

1
var p = Promise.all([p1,p2,p3]);
  • 接受一个数组作为参数
  • p1,p2,p3都是Promise对象的实例
  • p的状态由p1,p2,p3决定
  • 返回为一个新的promise实例

先个例子吧:

1
2
3
4
5
6
7
8
var p = [1, 2, 3].map((time) => {
return getPromise(time)
})
Promise.all(p).then((arrs) => {
console.log(arrs)
})
// 结果:[1,2,3]

Promise.race()

1
var p = Promise.race([p1,p2,p3]);

race与all方法不同之处在于p1,p2,p3之中有一个实例率先改变状态,p的状态就跟着改变。率先改变的Promise实例的返回值,就传递给p的回调函数。这里例子就不写了,有兴趣的同学可以去试试。

Promise.resolve()与Promise.reject()

这两个方法的特色之处是将其参数转换成一个新的Promise对象

错误的观念

有人用传统的写法去写then,虽然结果没有问题,但却脱离了我们创造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
// 错误的写法
getPromise(1)
.then((a) => {
console.log(a)
return getPromise(a+1)
.then((b) => {
console.log(b)
return getPromise(b+1)
.then((c) => {
console.log(c)
})
})
})
//结果:1
// 2
// 3
// 正确的写法
getPromise(1)
.then((a) => {
console.log(a)
return a+1
})
.then((b) => {
console.log(b)
return b+1
})
.then((c) => {
console.log(c)
return c+1
})
//结果:1
// 2
// 3

希望错误的同学能改正。对promise还不理解的可以参考阮一峰的文档 http://es6.ruanyifeng.com/#docs/promise