zoukankan      html  css  js  c++  java
  • 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也没就啥意义啦

    1 async function b(){
    2     return 1
    3 }
    4 b();

     可以看出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 }

  • 相关阅读:
    LCA——最近公共祖先
    P1576 最小花费
    CollaQ复现
    人体姿态估计Alphapose安装
    mingw安装
    MADDPG实现
    MFMARL(Mean Field Multi-Agent Reinforcement Learning)实现
    MASK_RCNN实现
    Insightface实现
    .tar.002文件怎么解压
  • 原文地址:https://www.cnblogs.com/zhanghaiyu-Jade/p/13592462.html
Copyright © 2011-2022 走看看