Promise Async/await Generator/yield 的使用

为了解决异步嵌套,写出更优雅,更易维护的代码,先后出现了ES6 提供的 Promise 方法,然后又是Generator/yield组合和 ES7 提供的 Async/Await 语法糖可以更好解决多层回调问题。

有了promise,为啥还要有Async/Await ?

虽然有了promise,链式调用更清晰了,但是需求复杂起来,就会发现代码充斥着.then,代码看起来也很吃力,这就出现了Async/Await解决了这个问题。还有Generator/yield接下来会一一介绍。

一,Promise

Promise表示一个异步状态的最终状态————>成功或者失败,成功通过resolve返回成功的值,失败通过reject返回错误信息。

Promise一旦创建就会立即执行

Promise有三种状态:

(1)pending:请求中...  

(2)fulfilled:完成状态 (从pending 到 fulfilled,resolve()接受成功返回的值,返回出去)

(3)rejected:拒绝状态(pending 到 rejected,reject()接受错误信息并返回出去,当有错误最好用reject(new Error("出错了")),更规范,也可以reject("出错了")直接字符串返回)

基本用法:

 1 let p = new Promise((resolve,reject) => {
 2     //............
 3     //成功状态
 4     if(条件成立){
 5         resolve('success')
 6     }else{
 7         reject(new Error("出错了"))
 8     }
 9    
10 });
11 p.then(res=> {
12     console.log(res);//成功
13 }).catch(err=>{
14     console.log(err);//失败
15 });  

Promise特点就是,状态一经确定为fulfilled或者rejected,就不会再变,状态就凝固了。

为什么说状态一经变化,就凝固了呢?看一下这个例子:

 1 let p1 = new Promise((resolve,reject) => {
 2     //............
 3     //成功状态 fulfilled状态
 4     resolve('success');
 5     console.log(1);
 6     //成功状态了,就不会改变了,所以下面这个状态不会变成拒绝状态
 7     reject(new Error("出错了"))   
 8 });
 9 
10 p1.then(res=> {
11     console.log(res);//成功
12 }).catch(err=>{
13     console.log(err);//失败
14 });  
15 //打印结果:1 success

其实resolve下面的代码可以执行,但是resolve()先执行的,所以此时promise的状态是fulfilled状态,因为状态一经确定就不会变了,所以,reject()在下面没有触发,是因为状态是fulfilled状态了。

then方法:

promise 原型上存在一个then方法——》Promise.prototype.then(),它的作用是为 Promise 实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例),因此可以采用链式写法

then有三种参数形式:

①.then(fun1,fun2) //fun1是成功了的回调,fun2是拒绝了(rejected)的回调

②.then(fun1) //fun1 是成功了的回调

③.then(null/undefined,fun2) //这个只接收拒绝了(rejected)回调,和catch 一样,都能用于指定发生错误时的回调函数。这种不太常用,更推荐使用catch

下面来看一下catch:

catch方法:

promise 原型上另一个方法——》Promise.prototype.catch(),catch()方法返回的还是一个 Promise 对象,后面还可以调用.then()方法

一般建议把catch放在链式的末尾,因为catch用于捕获它前面的异步中的错误,对于.catch()后面的链式调用(.catch().then())出现了错误就与catch()无关了,就捕获不到了

1 let p = new Promise((resolve,reject) => {
2    
3 });
4 p.then(res=> {
5    console.log(res);//成功
6 }).catch(err=>{
7     console.log(err);//失败
8 });

借用阮一峰阮老师的例子:

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

Error :test

等价写法如下:

 1 // 写法一
 2 const promise = new Promise(function(resolve, reject) {
 3   try {
 4     throw new Error('test');
 5   } catch(e) {
 6     reject(e);
 7   }
 8 });
 9 promise.catch(function(error) {
10   console.log(error);
11 });
12 //结果:Error test
13 // 写法二
14 const promise = new Promise(function(resolve, reject) {
15   reject(new Error('test'));
16 });
17 promise.catch(function(error) {
18   console.log(error);
19 });
20 //结果:Error test

比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

 1 const promise = new Promise(function (resolve, reject) {
 2   setTimeout(function () { throw new Error('test') }, 0);
 3   resolve('ok');
 4 });
 5 promise.then(function (value) {
 6   console.log(value) 
 7 }).catch(err=>{
 8   console.log("错误啦") 
 9 });
10 // ok
11 // Uncaught Error: test

上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

1 const promise = new Promise(function (resolve, reject) {
2   throw new Error('test');
3   resolve('ok');
4 });
5 promise.then(function (value) { console.log(value) }).catch(err=>{
6     console.log("错误")
7 });

这样就会正常捕获。

注意:catch()方法之中,还能再抛出错误,抛出去的错误,如果后面还有catch(),会被这个catch()捕获。

Promise.resolve()

将现有参数转换成Promise对象的快捷方式

Promise.resolve(1).then(res=>{
    console.log(res)//1
})

Promise.resolve()

获取一个拒绝状态的Promise对象的快捷方式

1 Promise.reject(1).catch(err=>{
2     console.log(err)//1
3 })

Promise.all([p1,p2,p3....])

p1,p2,p3都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve方法,将参数转为 Promise 实例,再进一步处理

参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。当所有的promise的状态都是成功状态才返回数组,只要其中有一个的对象是reject的,就返回reject的状态值。

 1 let p1 = Promise.resolve(123);
 2 let p2 = Promise.resolve('hello');
 3 let p3 = Promise.resolve('success');
 4 let p4 = Promise.reject('error');
 5 
 6 Promise.all([p1,p2,p4]).then(result => {
 7     console.log(result);
 8 }).catch(err=> {
 9     console.log(err);
10 });

error

Promise.race([p1,p2,p3....])

p1,p2,p3都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve方法,将参数转为 Promise 实例,再进一步处理

和all同样接受多个promise对象,race()接受的对象中,哪个对象返回的快就返回哪个对象,就如race直译的赛跑这样。如果对象其中有reject状态的,必须catch捕捉到,如果返回的够快,就返回这个状态。race最终返回的只有一个值。

Promise.allsettled([p1,p2,p3....])

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,才会结束。

该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

1 let p1 = Promise.resolve(123);
2 let p4 = Promise.reject('error');
3 
4 
5 Promise.allSettled([p1,p4]).then(result => {
6     console.log(result);
7 }).catch(err=> {
8     console.log(err);
9 });

 promise的优缺点:

​ 优点:

​ 1.Promise 分离了异步数据获取和业务逻辑,有利于代码复用。

​ 2.可以采用链式写法

​ 3.一旦 Promise 的值确定为fulfilled 或者 rejected 后,不可改变。

​ 缺点:

​ 代码冗余,语义不清。

二,Async/await

async function xxx (){}

async 声明的函数的返回本质是promise

1 (async function f(){
2    return "哈哈"
3 })()

 

 

 如果return 出来的不是promise ,默认会通过Promise.resolve("哈哈"),转换成promise对象

等同于:

1  (async function f(){
2     return Promise.resolve("哈哈")
3  })()

对待async 返回值像对待promise一样,结果用.then()链式获取

await 必须和async配合使用,await 必须放在async函数内部使用,等待一个异步方法执行完成返回结果,无需用then接收,直接返回结果出来。

async / await特点:

①async 后面的函数是异步的,返回一个promise对象,await必须放在内部使用

②await 在等待后面的promise返回结果后,再继续执行下面的代码

③await 后面最好是一个promise异步对象,敲黑板!!!是promise对象(settimeout这也是异步但是不可以),通过await去等待,当然是非异步的其他值也可以,只是直接执行罢了,但是用await也没就啥意义啦

async function b() {
    return 1
}
console.log(b()); 

 b()方法返回一个promise 对象 可以用.then()接收结果:

async function b() {
    return 1
}
b().then(res=>{
    console.log(res) //1
})

 可以看出async 后面的函数返回一个promise对象

例子:

 1 function p(wait){
 2     return new Promise((resolve,reject)=>{
 3         setTimeout(()=>{
 4             resolve(1);
 5         },wait)
 6     })
 7 }
 8 async function b(){
 9     console.log(222);
10     let res=await p(1000);
11     console.log(res);
12     console.log(333);
13 }
14 b();

先输出222,await暂停,然后等待p(1000)异步执行完毕,返回结果,在继续执行下面的代码,输出p(1000)的结果1,然后输出333

对比一下 promise 和async/await使用:

promise 解决了内嵌回调的问题

例子:

 1 function waitFn(wait) {
 2     return new Promise((resolve,reject) => {
 3         setTimeout(() => {
 4             resolve(wait);
 5         },wait);
 6     });
 7 }
 8 
 9 waitFn(1000).then(res1 => {
10     console.log("res1:"+res1);
11     return waitFn(res1+1000);
12 }).then(res2 => {
13     console.log("res2:"+res2);
14     return waitFn(res2+1000);
15 }).then(res3 => {
16     console.log("res3:"+res3);
17 })

等待1s输出res1:1000,等待2s输出res2:2000,等待3s输出res3:3000

用async/await 怎么写:

 1 function waitFn(wait) {
 2     return new Promise((resolve,reject) => {
 3         setTimeout(() => {
 4             resolve(wait);
 5         },wait);
 6     });
 7 }
 8 
 9 async function pro(){
10     let res1=await waitFn(1000);
11     console.log("res1:"+res1);
12     let res2=await waitFn(1000+res1);
13     console.log("res2:"+res2);
14     let res3=await waitFn(1000+res2);
15     console.log("res3:"+res3);
16 }
17 pro()

结果同样:等待1s输出res1:1000,等待2s输出res2:2000,等待3s输出res3:3000

是不是更简洁,更清晰了。

三,Generator/yield 

generator 函数function*()定义一个生成器函数,函数体内部使用yield表达式,这个生成器函数会返回一个generator对象。

引用阮老师的一个例子:

 1 function* helloWorldGenerator() {
 2   yield 'hello';
 3   yield 'world';
 4   return 'ending';
 5 }
 6 
 7 var hw = helloWorldGenerator();
 8 hw.next();//{value: "hello", done: false}
 9 hw.next();//{value: "world", done: false}
10 hw.next();//{value: "ending", done: true}
11 hw.next();//{value: undefined, done: true}

生成器函数不像普通函数,调用就会立即执行代码并返回结果,而是会返回一个生成器迭代对象(Iterator),这个迭代器对象有个next()方法,调用next()方法使得内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到yield表达式或者return语句就会停止,next()执行会返回一个对象,这个对象是当前yield表达式的值,这个对象包含两个属性,value和done,value表示本次yield表达式的值,done属性为布尔类型,表示生成器之后是否还有yield语句,即生成器是否已经执行完毕,如果done为true,表示遍历结束啦。done为false,就是下面还有yield语句,还没执行完毕,就会从本次yield继续执行,执行到下一个yield,next()同样返回一个对象包含{value:xx,done:true/false},value表示本次yield表达式的值,done属性为布尔类型,就这样重复遍历着,直到done为true,表示遍历结束啦。

yield表达式是暂停执行的标记,而next方法可以恢复执行。

第一次next()不用传参数,传参数也不会用到,第一次next()意思是启动的意思,当调用了第二次next()可以传递参数,参数就是上一次yield左边的变量的值。如果next()不传递参数,上一次yeild左边变量的值就是undefined。

引用阮老师的例子:

 1 function* foo(x) {
 2   var y = 2 * (yield (x + 1));
 3   var z = yield (y / 3);
 4   return (x + y + z);
 5 }
 6 
 7 var a = foo(5);
 8 a.next() // Object{value:6, done:false}
 9 a.next() // Object{value:NaN, done:false}
10 a.next() // Object{value:NaN, done:true}
11 
12 var b = foo(5);
13 b.next() // { value:6, done:false }
14 b.next(12) // { value:8, done:false }
15 b.next(13) // { value:42, done:true }

 yield和return异同:

①yield和return都可以返回值

②yield可以有暂停的作用,直到调用next(),内部的指针移到了该yield的表达式,才会继续执行。return不具备记忆功能。

③一个函数里面只有一个return 语句,而yield可以有多个。

④正常函数只能有一个返回值,只能执行一次return语句。Generator 函数可以返回一系列的值,因为可以有任意多个yield。

generator 函数function* (){}里面可以不放yield,但是不会执行,需要调用.next()方法才会执行

eg:

var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function* (a) {
  var length = a.length;
  for (var i = 0; i < length; i++) {
    var item = a[i];
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  }
};

for (var f of flat(arr)) {
  console.log(f);
}
// 1, 2, 3, 4, 5, 6

for....of和Symbol.iterator

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

 1 function* foo() {
 2   yield "a";
 3   yield "b";
 4   yield "c";
 5   yield "d";
 6   yield "e";
 7   return "f";
 8 }
 9 
10 for (let v of foo()) {
11   console.log(v);
12 }
13 // a b c d e 

js原生对象本来不具备Iterator接口,不能用for..of遍历。

例子:

1 let obj={"a":1,"b":2,"c":3};
2 console.log(obj)
3 console.log(obj[Symbol.iterator])
4 for(let item of obj){
5     console.log(item)
6 }

 

 

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

看下面这个例子:

 1 let obj={"a":1,"b":2,"c":3};
 2 obj[Symbol.iterator] = function* () {
 3   yield obj["a"];
 4   yield obj["b"];
 5   yield obj["c"];
 6 };
 7 console.log(obj);
 8 console.log(obj[Symbol.iterator]);
 9 for(let item of obj){
10     console.log(item)
11 }

 

 

 

 看上面这个例子就具备了Iterator接口,就可以用for...of遍历。

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

1 function* gen(){
2   // some code
3 }
4 
5 var g = gen();
6 
7 g[Symbol.iterator]() === g
//true

 

promise  , generator  ,async/await的使用例子:

老式的回调地狱:

1 step1(function (value1) {
2   step2(value1, function(value2) {
3     step3(value2, function(value3) {
4       step4(value3, function(value4) {
5         // Do something with value4
6       });
7     });
8   });
9 });

用promise改写:

 1 Promise.resolve(step1)
 2   .then(step2)
 3   .then(step3)
 4   .then(step4)
 5   .then(value4) {
 6     // Do something with value4
 7   })
 8   .catch(error=>{
 9     
10   })

用async/await改写:

1 async function task(value1) {
2     var value2 = await step1(value1);
3     var value3 = await step2(value2);
4     var value4 = await step3(value3);
5     var value5 = await step4(value4);
6     console.log(value5)
7 }

用generator 函数改写:

 1 function step(value){
 2    return value+1;
 3 }
 4 function* task(value) {
 5   try {
 6     var value2 = yield step(value);
 7     var value3 = yield step(value2);
 8     var value4 = yield step(value3);
 9     var value5 = yield step(value4);
10     // Do something with value4
11     console.log(value5);
12   } catch (e) {
13     // Handle any error from step1 through step4
14   }
15 }
16 go(task(1));
17 function go(task) {
18   var taskObj = task.next(task.value);
19   console.log(taskObj);
20   // 如果Generator函数未结束,就继续调用
21   if (!taskObj.done) {
22     task.value = taskObj.value;
23     go(task);
24   }
25 }

 

posted @ 2020-09-03 13:51  JadeZhy  阅读(900)  评论(0编辑  收藏  举报