Promise
那该多好 Lv2

Promise详解

Promise 基本用法

  • promise有三个状态:
        pending[待定] 初始状态   |   fulfilled[实现] 成功   |   rejected[被否决] 失败
    当promise状态发生改变时,就会触发then()里的响应函数处理后续步骤;
    promise 状态一经改变,状态固化,不会再改变
    Promise 有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected.一旦状态改变,就固化了,会一直保持这个状态,不会再发生变化。当状态发生变化时,promoise.then绑定的函数会被立即调用。
    new Promise() 是同步执行,一旦新建会「立即执行」,无法取消。

使用new构建一个Promise对象.Promise接受一个函数参数,该函数的两个参数分别是resolvereject.这两个函数就是回调函数.
resolve函数的作用: 在异步操作成功时调用,并将异步操作的结果作为参数传递出去;
reject函数: 在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去.

Promise实例生成后,可以用then方法指定resolved状态和rejected状态的回调函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 构建Promise
var promsie = new Promise( function(resolve, reject){
if(/*异步任务执行成功*/){
resovle()
}else{
/*异步任务执行失败*/
reject()
}
})

promise.then( function(data){
// resolve调用的 成功的回调函数
}, function(error){
// reject调用的 失败的回调函数
})

then方法会返回一个Promise对象,它有两个参数,分别为Promise从pending变为fulfilledrejected时的回调函数(第二个参数非必选). 这两个函数都接受Promise对象传出的值作为参数.
then就是定义resolvereject函数的,其resolve参数相当于:

1
2
3
function resolveFun(data){
// data为promise传出的值
}

新建Promise中的’resolve(data)’,相当于执行resolveFun函数.
Promise新建后就会立即执行.then方法中指定的回电函数,将当前脚本所有同步任务执行完成后才会执行.

1
2
3
4
5
6
7
8
9
10
11
12
var promise = new Promise( function(resolve, reject){
console.log(1);
resolve();
console.log(2);
})
promise.then( function(){
console.log(3);
})

console.log(4)

// 输出: 1, 2, 4, 3

由于resolve指定的是异步操作成功后的回调函数,他需要等所有同步代码执行完成后才会执行,因此最后打印 3。

基本API

.then()

语法: Promise.prototype.then(onFulfilled, onRejected)
对promise添加onFulfilledonRejected回调, 并返回的是一个新的Promise实例(不是.then前的那个Promise实例),且将返回值作为参数传入这个新的Promise对象的resolver函数
因此,我们可以使用链式写法。由于前一个回调函数返回的还是一个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
/* ajax异步回调 */
function request(url, param, successFun, errorFun){
$.ajax({
type:'GET',
url:url,
param:param,
async:true,
success: successFun,
error: errorFun
})
}
/* 构建promise实例 */
function test(url, param){
return new Promise( function(resolve, reject){
request(url, param, resolve, reject)
})
}
/* Promise的链式调用 */
test('test1.html', '').then( function(data1){
console.log('第一次请求成功',data1);
return test('test2.html',data1)
}).then( function(data2){
console.log('第二次请求成功', data2)
return test('test3.html', data2)
})
.then( function(data3){
console.log('第三次请求成功', data3)
}).catch( function(error){
// catch 捕捉前面的错误
console.log('请求失败', error)
})

.catch()

语法: Promise.prototype.catch(onRejected)
该方法是.then(null, onRejected)的另一种写法,用于指定发生错误时的回调函数.

1
2
3
4
5
promise.then( function(data){})
.cathc( function(error){})

/****** 等同于 ******/
promise.then( function(data){}, function(error){})
1
2
3
4
5
6
7
8
9
10
11
12
13
var promise = new Promise( function(resolve, reject){
throw new Error('error');
})
/****** 等同于 ******/
var promise = new Promise( function(resolve, reject){
reject(new Error('error'));
})

/* 用catch捕捉错误 */
promise.catch( function(error){
console.log(error)
})
// 输出: Uncaught (in promise) Error: error

reject等同于抛出错误。
promise对象的错误,会一直向后传递,直到被捕获。 即错误总会被下一个catch所捕获,then方法指定的回调函数若抛出错误,也会被下一个catch捕获. catch中也能抛错,则需要后面的catch捕获.

1
2
3
4
5
6
7
test('test1.html').then( function(data1){
// 要执行的操作
}).then(function(data2){
// 要执行的操作
}).catch( function(err){
// 处理前面三个Promise 产生的错误
})

上文提到过,promise状态一旦改变就会凝固,不会再改变。因此promise一旦fulfilled了,再抛错,也不会变为rejected,就不会被catch了。

1
2
3
4
5
6
7
var promise = new Promise( function(resolve, reject){
resolve();
throw 'error'
})
promise.catch( function(err){
console.log(err)
})

如果没有使用catch方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点

.all()

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

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

Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定。

  • 当p1, p2, p3状态都变为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是 resolved的顺序)存入数组,传给p的回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 3000, "first");
    });
    var p2 = new Promise(function (resolve, reject) {
    resolve('second');
    });
    var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, "third");
    });

    Promise.all([p1, p2, p3]).then(function(values) {
    console.log(values);
    });

    //约 3s 后 ["first", "second", "third"]
  • 当p1, p2, p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被reject的promise的返回值,传给p的回调函数,如例3.9。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var p1 = new Promise((resolve, reject) => { 
    setTimeout(resolve, 1000, "one");
    });
    var p2 = new Promise((resolve, reject) => {
    setTimeout(reject, 2000, "two");
    });
    var p3 = new Promise((resolve, reject) => {
    reject("three");
    });

    Promise.all([p1, p2, p3]).then(function (value) {
    console.log('resolve', value);
    }, function (error) {
    console.log('reject', error); // => reject three
    });

    // 输出 reject three

    这多个 promise 是同时开始、并行执行的,而不是顺序执行。从下面例子可以看出。如果一个个执行,那至少需要 1+32+64+128

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function timerPromisefy(delay) {
    return new Promise(function (resolve) {
    setTimeout(function () {
    resolve(delay);
    }, delay);
    });
    }
    var startDate = Date.now();

    Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
    ]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    console.log(values);
    });
    // 输出:133ms //不一定,但大于128ms
    // [1,32,64,128]

.race()

语法: Promise.race(iterable)
该方法同样是将多个Promise实例,包装成一个新的Promise实例

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

Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。

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
var p1 = new Promise(function(resolve, reject) { 
setTimeout(reject, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});

Promise.race([p1, p2]).then(function(value) {
console.log('resolve', value);
}, function(error) {
// 不执行
console.log('reject', error);
});
// 输出:resolve two

var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "four");
});

Promise.race([p3, p4]).then(function(value) {
// 不执行
console.log('resolve', value);
}, function(error) {
console.log('reject', error);
});
// 输出:reject four

在第一个promise对象变为resolve后,并不会取消其他promise对象的执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var fastPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('fastPromise');
resolve('resolve fastPromise');
}, 100);
});
var slowPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('slowPromise');
resolve('resolve slowPromise');
}, 1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([fastPromise, slowPromise]).then(function (value) {
console.log(value); // => resolve fastPromise
});
/**
* fastPromise
* resolve fastPromise
* slowPromise //仍会执行
*/

.resolve()

语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
可以看成 new Promise的快捷方式

1
2
3
4
5
6
Promise.resolve('Success');

/*******等同于*******/
new Promise(function (resolve) {
resolve('Success');
});

这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。

1
2
3
4
Promise.resolve('success').then(function (value) {
console.log(value);
});
// 输出: Success
1
2
3
4
5
6
7
8
9
10
11
//Resolving an array
Promise.resolve([1,2,3]).then(function(value) {
console.log(value[0]); // => 1
});

//Resolving a Promise
var p1 = Promise.resolve('this is p1');
var p2 = Promise.resolve(p1);
p2.then(function (value) {
console.log(value); // => this is p1
});

Promise.resolve()的另一个作用就是将thenable对象(即带有then方法的对象)转换为promise对象。

1
2
3
4
5
6
7
8
9
10
11
12
var p1 = Promise.resolve({ 
then: function (resolve, reject) {
resolve("this is an thenable object!");
}
});
console.log(p1 instanceof Promise); // => true

p1.then(function(value) {
console.log(value); // => this is an thenable object!
}, function(e) {
//not called
});

再看下面两个例子,无论是在什么时候抛异常,只要promise状态变成resolvedrejected,状态不会再改变,这和新建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
var p1 = { 
then: function(resolve) {
throw new Error("error");
resolve("Resolved");
}
};

var p2 = Promise.resolve(p1);
p2.then(function(value) {
//not called
}, function(error) {
console.log(error); // => Error: error
});

//在回调函数后抛异常
var p3 = {
then: function(resolve) {
resolve("Resolved");
throw new Error("error");
}
};

var p4 = Promise.resolve(p3);
p4.then(function(value) {
console.log(value); // => Resolved
}, function(error) {
//not called
});

.reject()

语法: Promise.reject(reason)
这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。

1
2
3
4
5
6
Promise.reject(new Error('error'));

/*******等同于*******/
new Promise(function (resolve, reject) {
reject(new Error('error'));
});

这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。

常见问题

reject 和 catch 的区别

  1. 使用new Promise(fn)或者它的快捷方式Promise.resolve()Promise.reject(),返回一个promise对象
  2. fn中指定异步的处理
    处理结果正常,调用resolve
    处理结果错误,调用reject
    一般情况,还是建议使用第二种,因为能捕获之前的所有异常。第二种的.catch()也可以使用.then()表示,它们本质上是没有区别的,.catch === .then(null, onRejected)

如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);

// 输出: Catch Error: A or B,ReferenceError: x is not defined
// 输出: Final Task

avatar
上面例子的输出结果及流程图,可以看出,A抛错时,会按照 taskA → onRejected → finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejected,taskB不会执行,直到catch了错误。

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
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejectedA(error) {
console.log("Catch Error: A", error);
}
function onRejectedB(error) {
console.log("Catch Error: B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.catch(onRejectedA)
.then(taskB)
.catch(onRejectedB)
.then(finalTask);

/**
* 输出:
* Catch Error: A ReferenceError: x is not defined
* Task B
* Final Task
* */

将本例与上面的例子对比,在taskA后多了对A的处理,因此,A抛错时,会按照A会按照 taskA → onRejectedA → taskB → finalTask这个流程来处理,此时taskB是正常执行的。

每次调用then都会返回一个新创建的promise对象,而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
//方法1:对同一个promise对象同时调用 then 方法
var p1 = new Promise(function (resolve) {
resolve(100);
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
console.log("finally: " + value);
});

// 输出: finally: 100

//方法2:对 then 进行 promise chain 方式进行调用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
// 输出: finally: 400

第一种方法中,then的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。
因此容易出现下面的错误写法:

1
2
3
4
5
6
7
8
9
10
11
12
function badAsyncCall(data) {
var promise = Promise.resolve(data);
promise.then(function(value) {
//do something
return value + 1;
});
return promise;
}
badAsyncCall(10).then(function(value) {
console.log(value); //想要得到11,实际输出10
});
//输出: 10

正确的写法应该是:

1
2
3
4
5
6
7
8
9
10
11
function goodAsyncCall(data) {
var promise = Promise.resolve(data);
return promise.then(function(value) {
//do something
return value + 1;
});
}
goodAsyncCall(10).then(function(value) {
console.log(value);
});
// 输出: 11

在异步回调中抛错,不会被catch到

1
2
3
4
5
6
7
8
9
10
// 异步函数中抛出的错误将像未捕获的错误一样
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});

promise.catch(function(e) {
console.log(e); //不会调用
});

promise状态变为resove或reject,就凝固了,不会再改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log(1);
new Promise(function (resolve, reject){
reject();
setTimeout(function (){
resolve(); //not called
}, 0);
}).then(function(){
console.log(2);
}, function(){
console.log(3);
});
console.log(4);

/**
* 输出:
* 1
* 4
* 3
* */

 评论