Promise 结合 async 和 await
上篇文章算是写了 Promise 的基础入门,这次结合 async 和 await 在对异步回调进行深入学习。
上篇文章: ES6 的 promise 和异步捕获错误 catch
一、Promise 深入学习
上篇文章写了 Promise 对异步回调黑洞的解救。但是却又陷入链式黑洞(或火车黑洞)。
- node fs 依次打开六个文件
- 例如我们用 node.js fs 模块依次打开六个 TXT 文件。
const fs = require("fs");
fs.readFile("./1.txt",function(err,data){
console.log(data.toString());
fs.readFile("./2.txt",function(err,data){
console.log(data.toString());
fs.readFile("./3.txt",function(err,data){
console.log(data.toString());
fs.readFile("./4.txt",function(err,data){
console.log(data.toString());
fs.readFile("./5.txt",function(err,data){
console.log(data.toString());
fs.readFile("./6.txt",function(err,data){
console.log(data.toString());
});
});
});
});
});
});
六首古诗
代码毕竟是给人看的,但上面的代码,结构松散,难于读取。
- 然后我们再用 Promise 改写。
const fs = require("fs");
function read(url){
return new Promise(function(resolve,reject){
fs.readFile(`./${url}.txt`,function(err,data){
if(err)return;
resolve(data.toString());
});
});
}
read(1).then(function(data){
console.log(data);
read(2).then(function(data){
console.log(data);
read(3).then(function(data){
console.log(data);
read(4).then(function(data){
console.log(data);
read(5).then(function(data){
console.log(data);
read(6).then(function(data){
console.log(data);
});
});
});
});
});
});
看了这个写法是不是要骂街了,这也没发挥出 Promise 作为语法糖的作用。所以如果是单纯异步回调改写成 promise 是没啥大区别。打败了回调黑洞进入链式黑洞。但是结合async 和 await 就大不一样了。
- 结合async 和 await 再次改写:
const fs = require("fs");
function read(url){
return new Promise(function(resolve,reject){
fs.readFile(`./${url}.txt`,function(err,data){
if(err)return;
resolve(data.toString());
});
});
}
async function main(){
var data1 = await read(1);
console.log(data1);
var data2 = await read(2);
console.log(data2);
var data3 = await read(3);
console.log(data3);
var data4 = await read(4);
console.log(data4);
var data5 = await read(5);
console.log(data5);
var data6 = await read(6);
console.log(data6);
}
main();
卧槽,这代码是绝了,结构简单逻辑清晰。看着就很舒服。代码最牛的是实现了 异步调用同步读取。而且省略了 then ,resolve 调用 then 里面的函数,函数的结果直接返回到等于号前面的 data 里面。
根据上面讲的可以发现 then 的功能被替代掉了,那还有没有 then 这个功能那?答案是有的比如我们依然依次打开三个 TXT 文件,每个文件里面都是一个 JSON 。
//a.txt
{"result":"I"}
=====
//b.txt
{"result":"Love"}
=====
//c.txt
{"result":"You"}
然后改写代码读取文件里面的 JSON 里面 result 的属性值。
const fs = require("fs");
function read(url){
return new Promise(function(resolve,reject){
fs.readFile(`./${url}.txt`,function(err,data){
if(err)return;
resolve(JSON.parse(data));
});
});
}
async function main(){
var data1 = await read("a").then(res=>res.result);
console.log(data1);
var data2 = await read("b").then(res=>res.result);
console.log(data2);
var data3 = await read("c").then(res=>res.result);
console.log(data3);
}
main();
I Love You
可见 then 还是可以使用的,要想对返回的结果进行处理,then 是必要的。
解说上面的 async 和 await 例子:我们做一个主函数,比如叫做 main(),给这个函数加上 async 关键字,表示这个函数里面有异步的语句需要等待。异步语句的前面,加上 await 字样,表示这条语句是异步语句,等待该 Promise 对象的 resolve() 方法执行并且返回值后才能继续执行。
async 和 await 含义?
async 用于声明一个异步函数, 该函数需返回一个 Promise 对象. 而 await 通常后接一个 Promise对象, 需等待该 Promise 对象的 resolve() 方法执行并且返回值后才能继续执行. (如果await后接的是其他对象, 便会立即执行) await 只能用于 async 声明的函数上下文中
如果不加 await 我们答应的结果就是一个Promise 对象。
不加 await
如果不加 async ,直接报错。
不加async
- animate 异步改写
我们在来个更牛的 JQuery 的异步函数 animate 改写成同步的。现在书写代码依次让四个盒子从左运动到右边。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>canvas事件</title>
<style>
div {
width: 60px;
height: 60px;
background-color: #0f0;
margin:10px;
position: relative;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
<script src="./js/jquery-3.4.1.min.js"></script>
<script>
$("div").eq(0).animate({"left":"600px"},2000);
$("div").eq(1).animate({"left":"600px"},2000);
$("div").eq(2).animate({"left":"600px"},2000);
$("div").eq(3).animate({"left":"600px"},2000);
</script>
animate 是异步的所以会一起运动。
想要实现四个盒子依次运动就得用 callback 回调函数来解决。
回调结果
$("div").eq(0).animate({"left":"600px"},2000,function(){
$("div").eq(1).animate({"left":"600px"},2000,function(){
$("div").eq(2).animate({"left":"600px"},2000,function(){
$("div").eq(3).animate({"left":"600px"},2000);
});
});
});
回调看着乱七八槽使用 Promise 代替
function move(number){
return new Promise(function(resolve,reject){
// 注意这里的 resolve 函数,是作为回调函数调用的
$("div").eq(number).animate({"left":"600px"},2000,resolve);
});
}
move(0).then(function(){
move(1).then(function(){
move(2).then(function(){
move(3).then(function(){});
});
});
});
Promise 看着也不是太好,现在用异步语句来实现同步调用。
function move(number){
return new Promise(function(resolve,reject){
$("div").eq(number).animate({"left":"600px"},2000,resolve);
});
}
async function main(){
await move(0);
await move(1);
await move(2);
await move(3);
}
main();
其实还可以在变,由Promise then 的写法变成 Generator 的 next 写法。generator 结合 async 使用。
function move(number){
return new Promise(function(resolve,reject){
$("div").eq(number).animate({"left":"600px"},2000,resolve);
});
}
async function* main(){
yield move(0);
yield move(1);
yield move(2);
yield move(3);
}
var g = main();
g.next();
g.next();
g.next();
g.next();
二、Generator 的使用
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
Generator 函数和普通函数其实很像,调用方法和 Promise 的调用方法也很像,下面代码直接调用 main() 函数,会返回一个 Generator 对象,对象在打点调用 next 方法,yield 就像函数里面的 return 把自己后面的值返回,这时变量接收 a 得到的返回状态值是一个对象 {value: "0", done: false}
,很明显 value 后面是yield 后面的返回值,done 是表示函数体里面的语句是否全部执行完毕。现在 var a = g.next();
只 next 函数了一个,函数现在暂停在第一个遇到的 yield 。继续 next 调用会开启第二个 yield 。
function* main(){
yield "0";
yield "1";
yield "2";
yield "3";
}
//g 现在是 Generator对象
var g = main();
// a现在也是一个对象值为{value: "0", done: false}
var a = g.next();
console.log(g,a);
现在让函数体内的语句全部执行完。通过 while 去判断 res 对象里面的属性 done 的值。
function* main(){
yield "0";
yield "1";
yield "2";
yield "3";
}
//g 现在是 Generator对象
var g = main();
// a现在也是一个对象值为{value: "0", done: false}
var res = g.next();
// 遍历函数体
while(!res.done){
console.log(res.value);
// 继续调用函数
res = g.next();
}
现在给函数体的开始加上 return 语句;这时返回结果value: 5, done: true}
return 后面的内容被返回,且函数体全部语句显示执行完毕。
function* main(){
return 5;
yield "0";
yield "1";
yield "2";
yield "3";
}
//g 现在是 Generator对象
var g = main();
// a现在也是一个对象值为{value: "0", done: false}
var res = g.next();
console.log(res);
现在把 return 语句拿到函数体的最下面;这时返回结果:
0
1
2
3
很明显函数体执行完 return 的内容没有接收到。
function* main(){
yield "0";
yield "1";
yield "2";
yield "3";
return 5 ;
}
//g 现在是 Generator对象
var g = main();
// a现在也是一个对象值为{value: "0", done: false}
var res = g.next();
while(!res.done){
console.log(res.value);
res = g.next();
}