[TOC]
理解Promise对象
Promise对象用于异步操作,表示一个现在、将来或永不可能可用的值。有如下几个特点:
- Promise对象代表了未来某个将要发生的事件(通常是一个异步操作);
- 通过Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数(回调地狱);
- Promise是一个构造函数,用来生成promise实例。
$.get('https://api.github.com/users', data => {
console.log('fetched all users');
const {login, url} = data[0];
$.get(`${url}/repos`, data => {
console.log('fetched user repos');
console.log(data);
});
});
2
3
4
5
6
7
8
在Promise之前,如果想要确保两个ajax请求的顺序,需要将后面的ajax放到之前ajax请求的回调中(随着回调函数的增加会形成回调地狱)。如上述代码所示:先获取所有的用户信息,然后再根据第一个用户信息来获取这个用户的所有仓库。上述代码通过Promise改下如下:
axios.get('https://api.github.com/users')
.then(response => {
const {login, url} = response.data[0];
return axios.get(`${url}/repos`);
}).then(response => {
console.log(response.data);
});
2
3
4
5
6
7
Promise用途
Promise
主要用于异步计算;- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果;
- 可以在对象之间传递和操作
Promise
,帮助我们处理队列。
Promise将多个异步回调改写成同步形式:
// Promise将异步回调以同步的形式展现出来
Promise.then(() => {
// 如果返回一个Promise实例。下一个then中回调会受上一个Promise的状态影响。
return new Promise();
}).then(() => {
return new Promise();
}).then(() => {
// 其它操作
});
2
3
4
5
6
7
8
9
状态响应函数可以返回新的Promise或其他值。如果返回新的Promise,那么下一级的then()
会在新Promise状态改变之后执行。如果返回其它任何值,则会立刻执行下一级then()
。
Promise
产生的原因?
浏览器中的javascript
- 异步操作以事件为主。
- 回调主要出现在
ajax
和file api
node.js
- 无阻塞高并发,是Node.js的招牌。
- 异步操作是其保障。
- 大量操作依赖回调函数。
异步回调有四个问题:
- 嵌套层次很深,难以维护,最终陷入陷入回调地狱;
- 无法正常使用
return和throw
; - 无法正常检索堆栈信息;
- 多个回调之间难以建立联系。
new Promise(
/*执行器 executor*/
(resolve, reject) => {
//一段耗时很长的异步操作
resolve(); //数据处理完成
reject(); //数据处理出错
}
).then(() => {
// 成功,下一步
}, () => {
// 失败,做相应处理
});
2
3
4
5
6
7
8
9
10
11
12
Promise
是一个代理对象,它和原先要进行的操作并无关系;Promise
通过引入一个回调,避免更多的回调。
Promise
对象通过自身的状态,来控制异步操作。Promise
有3个状态:
- pending:异步操作未完成;
- fulfilled:异步操作成功;
- rejected:异步操作失败;
需要注意:上面三种状态里面,fulfilled和rejected
合在一起称为resolved
,这三种的状态的变化途径只有两种。
- 从未完成到成功;
- 从未完成到失败。
Promise
状态发生改变,就会触发.then()
里的响应函数处理后续步骤;Promise
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise
这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise
实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
- 异步操作成功,
Promise
实例传回一个值(value
),状态变为fulfilled
; - 异步操作失败,
Promise
实例抛出一个错误(error
),状态变为rejected
。
需要注意:每一个then都会返回一个新的Promise。
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 2000);
}).then(value => {
console.log(value + ' world');
});
2
3
4
5
6
7
8
两步执行:
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 2000);
}).then(value => {
console.log(value);
return new Promise(resolve => {
setTimeout(() => {
resolve('world');
}, 2000);
});
}).then(value => { //两个then依次执行,value=上一个then的resolve的回调world
console.log(value + ' world');
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
结果如下:
here we go
hello
world world
2
3
Promise在队列中的应用:在任何地方生成了一个promise队列之后,我们可以把它作为一个变量传递到其他地方,如果我们的操作是队列的状态,即先进先出的状态,就可以在后面追加任意多的then,不管之前的队列是完成还是没有完成,队列都会按照顺序完成。
console.log('start');
let promise = new Promise(resolve => {
setTimeout(() => {
// 1秒后输出
console.log('the promise fulfilled');
resolve('hello');
}, 1000);
});
setTimeout(() => {
promise.then(value => {
console.log(value); // 3秒后输出
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
结果:
start
the promise fulfilled
hello
2
3
promise在then中不返回promise实例对象,将会执行下一步操作,即使返回值为false,false将会作为返回值传递到相应的then函数中。
console.log('here we go');
new Promise(resolve => { // 执行1
setTimeout(() => {
resolve('hello');
}, 2000);
}).then(value => {
console.log(value); // 执行2
console.log('everyone');
(function() { // 执行5。1、这段代码中没有返回新的值,下面一行返回的promise实际是在这个函数中返回的,不是在then的响应函数中返回的,then返回的promise实例就没有等待里面的这个promise完成。2、一直在等待执行,等最后的then返回之后,再执行这个函数。3、没有进入promise队列中,但是进程仍然是登它执行完成后才算是完成。
return new Promise(resolve => { // 自己用自己的回调
setTimeout(() => {
console.log('mr');
resolve('marry');
}, 2000)
});
}());
return false; // 执行3。1、false会直接传递到下一步,成为下一个then的value
// 即使这里将return false;注销,默认也会返回Undefined
})
.then(value => { // 执行4
console.log(value + 'world'); // value = false;
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
then方法
- .then()接受两个函数作为参数,分别代表fulfilled和rejected;
- .then()返回一个新的Promise实例,所以它可以链式调用;
- 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行。
- 状态响应函数可以返回新的Promise或其他值;
- 如果返回新的Promise,那么下一级.then()会在新Promise状态改变之后执行;
- 如果返回其它任何值,则会立刻执行下一级.then()。
.then()的嵌套
因为.then()
返回的还是Promise实例,外面的.then()
会等里面的.then()
执行完再执行。
console.log('start');
new Promise( resolve => {
console.log('Step 1');
setTimeout(() => {
resolve(100);
}, 1000);
}).then(value => {
console.log(value);
return new Promise(resolve => {
console.log('Step 1-1');
setTimeout(() => {
resolve(110);
}, 1000);
}).then(value => {
console.log('Step 1-2');
return value;
}).then(value => {
console.log('Step 1-3');
return value;
});
}).then(value => {
console.log(value);
console.log('Step 2');
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
执行结果:
start
Step 1
100
Step 1-1
Step 1-2
Step 1-3
110
Step 2
2
3
4
5
6
7
8
then中抛出错误
console.log(200);
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(a); // Uncaught ReferenceError: a is not defined
}, 100);
// reject();
console.log(100);
});
p.then(() => {
console.log('成功');
}, () => {
// 在then中的函数如果抛出异常,或者是报错,浏览器不会报错,而是直接走reject
console.log('111');
}).then(() => {
console.log('成功2');
}, () => {
console.log('失败');
});
console.log(300);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输出:
200
100
300
ReferenceError: a is not defined
2
3
4
console.log(200);
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 100);
console.log(100);
});
p.then(() => {
console.log('成功');
}, () => {
// 在then中的函数如果抛出异常,或者是报错,浏览器不会报错,而是直接走reject
console.log(a);
}).then(() => {
console.log('成功2');
}, () => {
console.log('失败');
});
console.log(300);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输出:
200
100
300
失败
2
3
4
then穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log); // 输出结果:1
2
3
4
解析: Promise.resolve 方法的参数如果是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为resolved,Promise.resolve 方法的参数,会同时传给回调函数。 then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会穿透下面。
错误处理
Promise会自动捕获内部异常,并交给rejected
响应函数处理-catch捕获。
推荐使用catch捕获
console.log('here we go');
new Promise( resolve => {
setTimeout( () => {
throw new Error('bye');
}, 2000);
}).then( value => {
console.log( value + ' world');
}).catch( error => {
console.log( 'Error:', error.message);
});
2
3
4
5
6
7
8
9
10
Promise会自动捕获内部异常,并交给rejected响应函数处理-reject响应捕获:
console.log('here we go');
new Promise((resolve, reject) => {
setTimeout(() => {
reject('bye');
}, 2000);
})
.then(value => {
console.log(value + ' world');
}, error => {
console.log('Error:', error);
});
2
3
4
5
6
7
8
9
10
11
catch和then连用
catch也会返回一个promise实例,如果没有抛出错误,也会是fulfilled
状态,会执行后面的then()
。
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
})
.then(() => {
console.log('start');
throw new Error('test error');
})
.catch(err => {
console.log('I catch:', err);
// 下面这一行的注释将引发不同的走向
// throw new Error('another error');
})
.then(() => {
console.log('arrive here');
})
.then(() => {
console.log('... and here');
})
.catch(err => {
console.log('No, I catch:', err);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
结果:catch
中不抛出错误,后面的then()
正常执行。
here we go
start
I catch: Error: test error
at Promise.then (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:9:15)
arrive here
... and here
2
3
4
5
6
catch
中抛出错误,后面的then()
不执行。
here we go
start
I catch: Error: test error
at Promise.then (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:9:15)
No, I catch: Error: another error
at Promise.then.catch.err (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:14:15)
2
3
4
5
6
强烈建议在所有队列最后都加上.catch()
,以避免漏掉错误处理造成意想不到的问题。
doSomething()
.doAnotherThing()
.doMoreThing()
.catch(err => {
console.log(err);
})
2
3
4
5
6
promises
的区别是什么?
下面的四种doSomething().then(function () {
return doSomethingElse();
});
doSomething().then(function () {
doSomethingElse();
});
doSomething().then(doSomethingElse());
doSomething().then(doSomethingElse);
2
3
4
5
6
7
8
9
10
11
假设
doSomething
和doSomethingElse
返回的都是一个Promise
实例。
// 问题一
doSomething()
.then(function () {
return doSomethingElse();
})
.then(finalHandler);
// 答案
// doSomething
// |-----------|
// doSomethingElse(undefined)
// |------------|
// finalHandler(resultOfDoSomethingElse)
// |------------|
// 问题二
doSomething()
.then(function () {
doSomethingElse();
})
.then(finalHandler);
// 答案
// doSomething
// |------------------|
// doSomethingElse(undefined)
// |------------------|
// finalHandler(undefined)
// |------------------|
// 问题三
doSomething()
.then(doSomethingElse())
.then(finalHandler);
// 答案
// doSomething
// |------------------|
// doSomethingElse(undefined)
// |----------------------------------|
// finalHandler(resultOfDoSomething)
// |------------------|
// 问题四
doSomething()
.then(doSomethingElse)
.then(finalHandler);
// 答案
// doSomething
// |-----------|
// doSomethingElse(resultOfDoSomething)
// |------------|
// finalHandler(resultOfDoSomethingElse)
// |------------------|
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
57