• TypeScript 之 Promise,Generator和 async


    工具: PlayGround


    简介


    ECMAScript 6 引入了异步操作的特性,主要特性有:

    • Promise 属于对象, 代表一个可能在未来完成的对象操作相关
    • Generator 属于函数, 可以通过 迭代器yield 来暂停函数的执行
    • async/await 属于操作符, 用于修饰 PromiseGenerator

    注:async/await 的引入不是ES6,而是ES8

    下面将详细的说明下。


    Promise对象


    先看下它的声明:

    interface Promise<T> {
      	/*
      	@func: 用于完成回调相关
      	@param onfulfilled 成功回调,它接受Promise的结果作为参数
      	@param onrejected 失败回调,它接受Promise的错误作为参数
      	@return 返回一个新的Promise对象
      	*/
        then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
      	/*
      	@func: 对与then函数失败状态的补充,用于捕获Promise中的错误
      	@param: 失败回调
      	@return 返回一个新的Promise对象
      	*/
        catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    简单的理解就是: 使用Promise创建异步对象执行,要么成功,要么失败。

    // 创建对象,参数: resolve表示成功时的回调函数, reject表示失败时的回调函数
    const obj = new Promise(function(resolve, reject) {
        resolve("sucess");
        reject("failed");
    })
    
    // 使用then执行成功回调
    obj.then((value:any) => {
        console.log(value);			// sucess
    })
    
    // 使用catch执行失败回调
    obj.catch((value:any) => {
        console.log(value);	
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意:

    • 对象创建后就会执行,无法停止,直到成功或失败。
    • 要设置 resolvereject 的回调方法,否则对象内部会抛出错误,且不会显示到外部

    then和catch

    他们是 Promise对象的主要方法,特点有:

    1. 可以放在一起使用
    const obj = new Promise((resolve, reject) => {
       resolve("sucess");
       reject("failed")
    });
    
    // 要么执行成功回调,要么执行失败回调
    obj.then((result) => {
        console.log("Promise resolved:", result);
    })
    .catch((error) => {
        console.error("Promise rejected:", error);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 可以将多个then方法链接在一起,他们会按照顺序并独立运行
    const obj = new Promise((resolve, reject) => {
        resolve(1);
    }).then((value) => {
        console.log("执行第1次回调", value);
        return value + 1;
    }).then((value) => {
        console.log("执行第2次回调", value);
    }).then((value) => {
        console.log("执行第3次回调", value);
        return Promise.resolve("resolve");
    }).then((value) => {
        console.log("执行第4次回调", value);
        return Promise.reject("reject");
    }).then((value) => {
        console.log("resolve:" + value);
    }, (error) => {
        console.log("reject:" + error);
    });
    
    /*
    // 创建时赋值1,将value+1的结果返回,并生成新的对象
    "执行第1次回调",  1 	
    // 新对象使用上个结果的参数2,无返回,并生成新的对象
    "执行第2次回调",  2
    // 新对象调用,上个参数因无返回则默认undefined, 生成新的对象并调用方法,赋值resolve
    "执行第3次回调",  undefined 	
    // 获取到回调参数结果...
    "执行第4次回调",  "resolve"
    "reject:reject" 
    */
    
    • 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

    这个代码可以拆开理解为:

    const obj = new Promise((resolve, reject) => {
        resolve(1);
    })
    
    const obj_1 = obj.then((value) => {
        console.log("执行第1次回调", value);
        return value + 1;
    })
    
    const obj_2 = obj_1.then((value) => {
        console.log("执行第2次回调", value);
    })
    
    const obj_3 = obj_2.then((value) => {
        console.log("执行第3次回调", value);
        return Promise.resolve("resolve");
    })
    
    const obj_4 = obj_3.then((value) => {
        console.log("执行第4次回调", value);
        return Promise.reject("reject");
    })
    
    const obj_5 = obj_4.then((value) => {
        console.log("resolve:" + value);
    }, (error) => {
        console.log("reject:" + error);
    })
    
    /*
    "执行第1次回调",  1 
    "执行第2次回调",  2 
    "执行第3次回调",  undefined 
    "执行第4次回调",  "resolve"
    "reject:reject" 
    */
    
    • 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

    注: 每次的独立运行都会生成一个新的Promise对象,且接受上一个对象的结果作为参数

    1. 可以将多个catch方法链接在一起使用,使得错误依次进行。
    const promise = new Promise((resolve, reject) => {
      // 模拟异步操作
      setTimeout(() => {
        const randomNumber = Math.random();
        if (randomNumber < 0.5) {
          resolve("Operation completed successfully");
        } else {
          reject("Operation failed");
        }
      }, 2000);
    });
    
    promise
      .then((result) => {
        console.log("Promise resolved:", result);
      })
      .catch((error) => {
        console.error("First catch:", error);
        throw new Error("Custom error");
      })
      .catch((error) => {
        console.error("Second catch:", error.message);
      });
    
    // "First catch:",  "Operation failed" 
    // "Second catch:",  "Custom error" 
    
    • 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

    Generator函数


    主要通过yield将执行流程挂起, 通过next()方法执行下一步。 同普通函数区分主要有:

    • 函数是否存在yield表达式相关
    • 定义一定要使用function*

    在创建Generator函数后,它并不会立即执行,而是返回一个迭代器对象;该对象可以通过next()方法逐步执行函数内部的代码。

    function* loopFunc() {
      for (let i = 0; i < 2; ++i) {
        console.log("value:", i);
        yield i;
      }
    }
    
    const func = loopFunc();
    const data1 = func.next();  // "value:",  0
    console.log(data1);         // {"value": 0,"done": false} 
    const data2 = func.next();  // "value:",  1 
    console.log(data2);         // {"value": 1,"done": false}
    
    // 注意此处,函数已经执行完毕
    const data3 = func.next();
    console.log(data3);         // {"value": undefined,"done": true} 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    通过next()方法会返回两个对象:

    • value 表示函数yield表达式产生的值
    • done 表示函数是否执行完毕, true表示执行完毕,false表示函数还可以继续执行。

    next函数

    next()方法一般情况下是不会传入参数的,但支持参数传入,它的定义:

    Generator<number, void, unknown>.next(...args: [] | [unknown]): IteratorResult<number, void>
    
    • 1

    如果传入参数会作为上一个yield语句的返回值。

    function* myGenerator() {
      let result = yield;
      yield result + 10;
    }
    
    const gen = myGenerator();
    // 第一次没有返回值, 故此value默认为undefined
    console.log(gen.next()); // { value: undefined, done: false }
    // 第二次参数5作为上一个yield的返回值,故此value为15
    console.log(gen.next(5)); // { value: 15, done: false }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为Generator函数返回的是迭代器对象,所以同样可以使用for ...of进行循环遍历

    function* myGenerator() {
      yield 1;
      yield 2;
      yield 3;
    }
    
    let result: string = ""
    for (const value of myGenerator()) {
      result += value;
    }
    console.log(result);    // "123" 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    return函数

    Generator函数可以使用next()方法获取下一步的执行结果, 也可以使用return()来结束函数的执行。

    function* demo() {
      yield 1;
      yield 2;
      yield 3;
    }
    // 实例1:
    const myGen1 = demo();
    console.log(myGen1.next());    // {"value": 1,"done": false}
    console.log(myGen1.next());    // {"value": 2,"done": false}
    console.log(myGen1.next());    // {"value": 3,"done": false}
    
    // 实例2:
    const myGen = demo();
    console.log(myGen.next());    // {"value": 1,"done": false}
    console.log(myGen.return());  // {"value": undefined,"done": true}
    console.log(myGen.next());    // {"value": undefined,"done": true}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    两个例子进行对比,会发现return()方法强制终止了函数的执行。


    yield* 表达式

    它可用于在Generator函数中委托另一个Generator函数或可迭代对象的过程。

    function* generator1() {
      yield "a";
      yield "b";
    }
    
    function* generator2() {
      yield* generator1();
      yield "c";
      yield "d";
    }
    
    const gen = generator2();
    console.log(gen.next()); // { value: 'a', done: false }
    console.log(gen.next()); // { value: 'b', done: false }
    console.log(gen.next()); // { value: 'c', done: false }
    console.log(gen.next()); // { value: 'd', done: false }
    console.log(gen.next()); // { value: undefined, done: true }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    至此,大概的用法介绍完毕,Generator函数了解到这几点大概够用了。



    async和await 操作符


    这两个关键字是从ES2017(ES8)版本引入的,与Promise对象和Generator函数有很大的关联,故此放到了一起来说明。

    • await 只能在异步函数内部使用,可用于等待一个Promise对象
    • async 用于声明函数是异步的, 函数内部可使用await来暂停函数的执行。

    简单的实例:

    // 实例1
    async function demo1() {
      console.log(1);
      console.log(2);
    }
    demo1();
    console.log(3);       // 1,2,3
    
    // 实例2
    async function demo2() {
      await 1;
      console.log(1);
      console.log(2);
    }
    demo2();
    console.log(3);       // 3,1,2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    关于async, await和Promise对象的实例:

    function delay(ms: number): Promise<string> {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve("Delayed message");
        }, ms);
      });
    }
    
    async function asyncFunction() {
      console.log("Before await");
      const result = await delay(2000);
      console.log(result);
      console.log("After await");
    }
    
    asyncFunction();
    
    // "Before await"  先输出
    // "Delayed message" 等待两秒钟后输出
    // "After await" 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    至此异步操作相关结束。


    注意事项


    异步编程在实际的应用开发中需要注意:

    1. 推荐使用async/await进行处理,使得代码易读和容易维护
    2. 使用try/catch捕获和处理异步操作中的异常
    3. 避免多层回调嵌套
    4. 注意异步操作的吮吸,避免处理逻辑问题

    使用try/catch捕获异常的的简单示例:

    function run(): Promise<void> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                const success = true; // 模拟异步操作成功或失败的情况
                if (success) {
                    resolve();
                } else {
                    reject(new Error("Failed to fetch data"));
                }
            }, 1000);
        });
    }
    
    async function startRun() {
        try {
            await run();
            console.log("run successfully");
        } catch (error) {
            console.error(error && error.message);
        }
    }
    
    startRun();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    【读书笔记】【Effective C++】实现
    基于图神经网络的图像分类,遥感图像分析
    “微软爱写作”连词摘录
    电脑重装系统后Win11用户账户控制设置怎么取消
    【C++】C++ 引用详解 ⑩ ( 常量引用案例 )
    网络层重点协议——IP协议
    redis主从中的Master自动选举之Sentinel哨兵机制
    ThreadPoolExecutor
    Hadoop3:客户端向HDFS写数据流的流程讲解(较枯燥)
    【深度学习】手写数字识别
  • 原文地址:https://blog.csdn.net/qq_24726043/article/details/132814267