导航
导航
文章目录
  1. 说说promise
  2. Promise的表现
  3. 用法
  4. 异步嵌套回调
  5. promise.all方法
  6. promise.race方法
  7. 一道常见面试题
  8. 情景

浅谈Promise

说说promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

所谓Promise,简单说就是一个容器(对象),里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

接触过promise的的都知道它的应用场景和用途,Promise可以用来避免异步操作函数里的嵌套回调(callback hell)问题,因为解决异步最直接的方法是回调嵌套,将后一个的操作放在前一个操作的异步回调里,但如果操作多了,就会有很多层的嵌套(回调地狱)。

1
2
3
4
5
6
7
8
9
10
$.ajax(url1, function(data1){
// do something...
$.ajax(url2, function(data2){
// do something...
$.ajax(url3, function(data3){
// do something...
done(data3); // 返回数据
})
});
});

Promise学术点的描述:

promise代表一个异步操作的执行返回状态,这个执行返回状态在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。

这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回状态的 promise 对象来替代原返回状态。

Promise的表现

如果使用回调方法处理多个操作的异步场景,判断某个操作成功或失败的控制在于声明的匿名函数里面,使用Promise对象则可以重新定义异步执行的状态和控制逻辑。

promise的最重要的特点就是它把我们处理任何函数调用的成功或者失败的方式规范成了可预测的形式,特别是如果这个调用实际上的异步的。

Promise中有几个状态:

  • pending: 初始状态。 非 fulfilled 或 rejected。
  • resolved: 成功的操作。也有的成为fulfilled 。
  • rejected: 失败的操作。

状态转换关系为:

pending->resolved(fulfilled),pending->rejected。

Promise对象有以下两个特点:

  • 对象的状态不受外界影响,Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。

用法

说了这么多,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise((resolve, reject) => {
// do somthing, maybe async
if (success){
return resolve(res);
} else {
return reject(err);
}
});

promise.then(res => {
// do something... e.g
console.log(res);
}, err => {
// deal the err.
})

或封装成方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fetch(data) {
return new Promise((resolve, reject) => {
// do somthing, maybe async
if (success){
resolve(res);
} else {
reject(err);
}
})
}

fetch(data)
.then(res => {
console.log(res)
}, err => {
// deal the err.
})

异步嵌套回调

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
function loadAsync1(){
return new Promise((resolve, reject) => {
//异步操作
setTimeout(() => {
console.log('异步任务1');
resolve('异步任务1传过来的值');
}, 2000);
});
}
function loadAsync2(data1){
return new Promise((resolve, reject) => {
//异步操作
setTimeout(() => {
console.log('异步任务2');
resolve('异步任务2传过来的值');
}, 2000);
});
}
function loadAsync3(data2){
return new Promise((resolve, reject) => {
//异步操作
setTimeout(() => {
console.log('异步任务3');
resolve('异步任务3传过来的值');
}, 2000);
});
}

有返回值

1
2
3
4
5
6
7
8
loadAsync1()
.then(data1 => {
return loadAsync2(data1)
})
.then(data2 => {
return loadAsync3(data2)
})
.then(okFn, failFn)

没有返回值

1
2
3
4
5
6
7
8
loadAsync1()
.then(data1 => {
loadAsync2(data1)
})
.then(data2 =>{
loadAsync3(data2)
})
.then(res => console.log(res))

输出的值为:

异步任务1
异步任务1传过来的值
异步任务2
异步任务2传过来的值
异步任务3
异步任务3传过来的值

promise.all方法

Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var p1 = new Promise((resolve) => {
setTimeout(() => {
resolve("第一个promise");
}, 3000);
});

var p2 = new Promise((resolve) => {
setTimeout(() => {
resolve("第二个promise");
}, 1000);
});

Promise.all([p1, p2])
.then((result) => {
console.log(result); // ["第一个promise", "第二个promise"]
});

上面的代码中,all接收一个数组作为参数,p1,p2是并行执行的,等两个都执行完了,才会进入到then,all会把所有的结果放到一个数组中返回,所以我们打印出我们的结果为一个数组。

值得注意的是,虽然p2的执行顺序比p1快,但是all会按照参数里面的数组顺序来返回结果。all的使用场景类似于,玩游戏的时候,需要提前将游戏需要的资源提前准备好,才进行页面的初始化。

promise.race方法

Promise.race 可以接收一个元素为 Promise 对象的数组作为参数,这个数组里面所有的 Promise 对象进行竞速,完成一个即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var p1 = new Promise((resolve) => {
setTimeout(() => {
console.log('异步任务1执行完成');
resolve("第一个promise");
}, 3000);
});

var p2 = new Promise((resolve) => {
setTimeout(() => {
console.log('异步任务2执行完成');
resolve("第二个promise");
}, 1000);
});

Promise.race([p1, p2])
.then((result) => {
console.log(result);
});

//异步任务2执行完成
//第二个promise
//异步任务1执行完成

在then里面的回调开始执行时,p1 并没有停止,仍旧在执行。于是再过2秒后,输出了他们结束的标志。

这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

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 requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}

//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(err){
console.log(err);
});

requestImg函数会异步请求一张图片,我把地址写为”xxxxxx”,所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。

一道常见面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
setTimeout(function() {
console.log(1)
}, 0);

new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});

console.log(5);

2 3 5 4 1

情景

传入一个token,根据这个token请求一次网络,然后获取用户ID,将获取的用户ID访问数据库,获取用户信息

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
var request = function (token) {
return new Promise((resolve, reject)=> {
setTimeout(()=> {
token ? resolve(2) : reject('token error');
},1000)
});
};

var find = function (id) {
return new Promise((resolve, reject)=> {
setTimeout(()=> {
id ? resolve(id + '-info') : reject('id error');
},1000)
});
};

request('token')
.then(function (id) {
return find(id);
})
.then(function (info) {
console.log( info);
});

co(function *() {
var id = yield request('token');
var info = yield find(id);

console.log(id, info);
});

(async function () {
var id = await request('token');
var info = await find(id);

console.log(id, info);
})();

之后会有express操作mongondb的基于Promise的封装,敬请期待!