Content Table

Promise 知识点

可以访问 Promise 对象基础 对 Promise 进行了解,这里就不再赘述了。

Promises 承诺,你将会得到延期或长期运行任务的未来结果。承诺有两个渠道:第一个为结果,第二个为潜在的错误。要获取结果,您将回调函数作为 then 函数参数,要处理错误,您将回调函数提供为 catch 函数参数。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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>

<body>
<script type="text/javascript">
let p = new Promise((resolve, reject) => {
setTimeout(function() {
const result = Math.random();
result > 0.5 ? resolve(result) : reject('Lower than 0.5');
}, 2000);
});

p.then(result => {
console.log('success', result);
}).catch(result => {
console.log('fail', result);
});
</script>
</body>

</html>

Promise.all

Promise.all() 返回一个 promise 对象,接收一个 Promise 对象的数组作为参数,当这个数组里的所有 Promise 对象全部变为 resolved 的时候该 promise 对象的状态才为 resolved,如果其中一个 Promise 对象为 rejected 的话则该 promise 的状态为 rejected。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function timerPromisefy(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}

Promise.all([
timerPromisefy(32),
timerPromisefy(1),
timerPromisefy(64),
timerPromisefy(2),
timerPromisefy(256),
]).then(results => {
console.log(results); // 输出: [ 32, 1, 64, 2, 256 ]
});

Promise.race

Promise.race() 使用方法和 Promise.all() 一样,接收一个 Promise 对象数组为参数,只要其中一个 Promise 对象变为 resolved 或 rejected 状态,执行后面的 then 或者 catch 回调函数。

Promise 的处理逻辑

Promise 的调用链有很多种情况,但总结起来就一句话: then 和 catch 都会返回一个 promise 对象,如果我们没有手动的返回 promise 对象,那么就会自动地把返回值封装为一个 resolved 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
35
36
37
38
39
40
41
42
43
44
45
46
47
// then 的第一个参数是正确的回调函数,第二个参数是错误的回调函数
Promise.resolve()
.then(() => {
// [1]: 如果没有返回 Promise 对象,内部会自动返回一个 Promise.resolve(),马上执行下一个 then
// [1.1] 没有返回语句,Promise.resolve() 的参数为 undefined
// [1.2] 当有返回语句,Promise.resolve() 的参数为 return 的值
console.log(1);
// return 111; // 去掉注释查看结果
})
.then((x) => {
// [2] 明确返回 resolved promise,与 [1.1] 效果一样
console.log(2, x); // undefined 或者 111
return Promise.resolve();
})
.then(() => {
console.log(3);

// [3] 明确返回 Promise 对象,当执行 resolve 或者 reject 后才执行下一个 then 或者 catch
// 等待 1 秒后执行 resolve,然后马上下一个 then
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// [4] 返回 rejected promise,则执行离他最近的一个错误处理回调函数
console.log(4);
return Promise.reject();
})
.then(() => {
console.log(5); // 注意: 跳过
}, () => {
// [5] 处理 [4] 中的错误,内部返回 resolved promise,然后继续执行下一个 then
console.log('Error 1');
})
.then(() => {
// [6] 抛出异常时,则执行离他最近的一个错误处理回调函数 (此处为最后的 catch)
console.log(6);
throw 'thrown';
})
.then(() => {
console.log(7); // 注意: 跳过
})
.catch((error) => {
console.log('Error final', error);
});

输出:

1
2
3
4
5
6
7
1
2 undefined
3
4
Error 1
6
Error final thrown

async and await

Async 和 await 的作用是简化 promise then 的链式调用,相当于语法糖,缺点是中间不能处理异常:

  • await 必须在 async 函数中
  • async 函数会被异步执行,但是 aync 函数内的代码阻塞执行
  • async 函数返回 promise,如果返回的不是 promise 对象,则会自动把返回结果封装为 promise 返回
  • await 只是 promise then 的简写,得到的结果为 then 的参数,即 promise resolve 的实参
  • await 如果出错,可以使用 try catch 进行处理,也可以在 promise 的 catch 中处理

视频 https://www.bilibili.com/video/BV1xW411J7K6 对 async and await 介绍的不错,有兴趣的话可以看看。

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
function get() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Date.now() % 2 === 0) {
resolve('---get');
} else {
reject(Error('--get fail'));
}
}, 1000);
});
}

function post() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('---post');
}, 500);
});
}

async function handle() {
console.log(1);

const result1 = await get();
console.log(result1);

console.log(2);

const result2 = await post();
console.log(result2);

console.log(3);

return 4; // 非 promise 返回值为会被封装为 promise 对象
}

console.log('from');

handle()
.then((x) => {
console.log('after handle', x);
})
.catch((error) => {
console.log(error);
});

console.log('to');

Vue 的 methods 中定义的函数也支持 async await,需要注意的是 created 和 mounted 不支持。

也可以使用自执行函数调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
(async () => {
const r1 = await funP1();
const r2 = await funP2(r1);
})();

(async () => {
try {
const r1 = await funP1();
const r2 = await funP2(r1);
} catch (error) {
// handle error
}
})();

防止回调地狱

当一个请求结束后再执行下一个请求,如果用 callback,层次会很深,用 Promise 只需要在链式的 then 中处理上一个 then 中返回的 promise 的结果,防止回调地狱,最后的 catch 处理所有 promise 的 reject,中间任何一个 promise 执行了 reject 就会中断后续操作,async and await 能够使得 promise then 的调用链更简洁。

例如修改用户头像的操作:

  1. 拍照: capture
  2. 裁剪图片: crop
  3. 压缩图片: compress
  4. 上传图片到服务器: upload
  5. 检测图片中是否有人脸: detectFace
  6. 有人脸则更新头像: updateAvatar

如果用回调的方式实现上面的代码,则结构可能为 (当对回调的结果要进行 if else 多种判断时,情况会更复杂):

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
// 拍照
function capture(callback) {
Camera.capture((blobImage) => {
callback(blobImage);
})
}

// 裁剪图片
function crop(blobImage, callback) {
Cropper.crop(blobImage, (croppedBlobImage) => {
callback(croppedBlobImage);
})
}

// 压缩图片
function compress(blobImage, callback) {
Compressor.compress(blobImage, '200px', '200px', (compressedBlobImage) => {
callback(compressedBlobImage);
})
}

// 上传图片到服务器
function upload(blobImage, callback) {
ajax.post(blobImage).success((response) => {
callback(response.imageUrl);
});
}

// 检测图片中是否有人脸
function detectFace(imageUrl, callback) {
ajax.get(imageUrl).success((response) => {
callback(response.result); // true or false
})
}

// 更新头像
function updateAvatar(userId, imageUrl) {
ajax.post(userId, imageUrl);
}

// 更新头像,多级回调
capture((image) => {
crop(image, (croppedBlobImage) => {
compress(croppedBlobImage, (compressedBlobImage) => {
upload(compressedBlobImage, (imageUrl) => {
detectFace(imageUrl, (success) => {
if (success) {
updateAvatar(userId, imageUrl);
} else {
alert('没有检测到人脸');
}
});
});
});
});
});

使用 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 拍照
function capture() {
return new Promise((resolve, reject) => {
Camera.capture((blobImage) => {
resolve(blobImage);
});
});
}

// 裁剪图片
function crop(blobImage) {
return new Promise((resolve, reject) => {
Cropper.crop(blobImage, (croppedBlobImage) => {
resolve(croppedBlobImage);
});
});
}

// 压缩图片
function compress(blobImage) {
return new Promise((resolve, reject) => {
Compressor.compress(blobImage, '200px', '200px', (compressedBlobImage) => {
resolve(compressedBlobImage);
});
});
}

// 上传图片到服务器
function upload(blobImage) {
return new Promise((resolve, reject) => {
ajax.post(blobImage).success((response) => {
resolve(response.imageUrl);
});
});
}

// 检测图片中是否有人脸
function detectFace(imageUrl) {
return new Promise((resolve, reject) => {
ajax.get(imageUrl).success((response) => {
if (response.result) {
resolve();
} else {
reject();
}
})
});
}

// 更新头像
function updateAvatar(userId, imageUrl) {
return new Promise((resolve, reject) => {
ajax.post(userId, imageUrl).success(() => {
resolve();
});
});
}

// 更新头像,promise 解决回调地狱
capture()
.then(blobImage => {
return crop(blobImage);
})
.then(croppedBlobImage => {
return compress(croppedBlobImage);
})
.then(compressedBlobImage => {
return upload(compressedBlobImage);
})
.then(imageUrl => {
return detectFace(imageUrl);
})
.then(imageUrl => {
return updateAvatar(userId, imageUrl);
})
.catch(error => {
console.log(error);
});

使用 async and await 简化上面 promise 的代码,看上去更简洁、逻辑更清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function handle() {
try {
const image = await capture();
const croppedImage = await crop(image);
const compressedImage = await compress(croppedImage);
const imageUrl = await upload(compressedImage);
await detectFace(imageUrl); // 检测到人脸才执行 updateAvatar 更新头像
await updateAvatar(imageUrl);
} catch (error) {
console.log(error);
}
}

// 调用 handle 后继续执行下面的函数,而不会阻塞到 handle 执行完成才继续往下执行
// 而 handle 内部阻塞按顺序执行
handle();