zoukankan      html  css  js  c++  java
  • ES6 异步编程解决方案 之 Promise 对象

    一、Promise 概述

    • Promise 对象是 ES6 提供的原生的内置对象

    • Promise 是异步编程的一种解决方案(异步代码同步化),比传统的解决方案——回调函数和事件——更合理和更强大

    • Promise 对象代表一个异步操作, 其不受外界影响,有三种状态:

      • Pending(进行中、未完成的)
      • Resolved(已完成,又称 Fulfilled)
      • Rejected(已失败)

    二、Promise 对象的 优缺点

    1. 优点

    • 异步编程解决方案,避免了回调地狱(Callback Hell)问题
    // 回调地狱
    
    firstAsync(function(data){
        //处理得到的 data 数据
        //....
        secondAsync(function(data2){
            //处理得到的 data2 数据
            //....
            thirdAsync(function(data3){
                  //处理得到的 data3 数据
                  //....
            });
        });
    });
    
    // 使用 Promise 解决方案
    
    firstAsync()
    .then(function(data){
        //处理得到的 data 数据
        //....
        return secondAsync();
    })
    .then(function(data2){
        //处理得到的 data2 数据
        //....
        return thirdAsync();
    })
    .then(function(data3){
        //处理得到的 data3 数据
        //....
    });
    

    2. 缺点

    • Promise 对象,一旦新建它就会立即执行,无法中途取消
    let Promise = new Promise(function(resolve, reject) {
      console.log('Promise');
      resolve();
    });
    
    Promise.then(function() {
      console.log('resolved.');
    });
    
    console.log('Hi!');
    
    // Promise
    // Hi!
    // resolved
    
    • Promise 状态一旦改变,就不会再变
    const Promise = new Promise(function(resolve, reject) {
        resolve('ok');
        throw new Error('test');
    });
    Promise
        .then(function(value) { console.log(value) })
        .catch(function(error) { console.log(error) });
    
    // 只输出 ok
    
    • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

    三、Promise 的基本用法

    • ES6 规定,Promise对象是一个构造函数,用来生成Promise实例

    • 语法:

      • Promise 构造函数:接收一个函数参数,函数参数 中有两个参数 resolvereject

      • 参数 resolvereject 都是函数(由 JavaScript 引擎提供,不用自己部署),在 异步操作成功时,手动执行 函数resolve在 异步操作失败时,手动执行 函数reject

      • Promise实例生成以后,可以用 thencatch 方法分别指定 成功状态、失败状态的回调函数

    const promise = new Promise(function(resolve, reject) {
        // 异步操作 code
    
        // 手动执行 resolve / reject 函数
        if (/* 异步操作成功 */){
            resolve(value);
        } else {
            reject(error);
        }
    });
    
    promise
      .then((value) => {})
      .catch((error) => {});
    
    • 异步编程解决方案: 将异步任务同步操作(一般情况下 Promise 对象 集结合 then、catch 使用)

      • 异步任务:在 创建 Promise 对象中 执行( Promise 对象中的代码 会立即执行

      • 需要在异步任务之后执行的代码:放到 then、catch 中

    四、Promise 原型上的 方法

    1. then 方法

    • 作用: 为 Promise 实例添加 状态改变(成功、失败) 时的回调函数

    • 语法: Promise.then(resolve, reject)

      • 参数:

        • 参数 resolve:异步操作成功时的回调函数

        • 参数 reject:异步操作失败时的回调函数(可省略)

      • 返回值: 一个新的Promise实例(不是原来那个Promise实例),可采用链式写法

    getJSON("/posts.json").then(function(json) {
      return json.post;
    }).then(function(post) {
      // ...
    });
    
    // 如上:第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数
    
    • 返回值 有可能是 含有异步操作的 Promise 对象, 这时之后的 then 方法中的函数,就会等待该 Promise 对象的状态发生变化,才会被调用
    const getPromise = () => {
        const Promise = new Promise(function (resolve, reject) {});
    
        return Promise;
    }
    
    getPromise().then(
        param => getPromise(param)
    ).then(
        comments => console.log("resolved: ", comments),
        err => console.log("rejected: ", err)
    );
    

    2. catch 方法

    • 作用: 为 Promise 实例添加 状态变为 reject 时 的回调函数

    • 语法: Promise.catch(reject)

    • 等同于: Promise.then(null, reject)

    // 示例如下(Promise抛出一个错误,就被catch方法指定的回调函数捕获)
    
    const Promise = new Promise(function(resolve, reject) {
      throw new Error('test');
    });
    
    Promise.catch(function(error) {
      console.log(error);
    });
    // Error: test
    
    • Promise 对象的错误具有“冒泡”性质: 会一直向后传递,直到被捕获到为止
    const getPromise = () => {
        const Promise = new Promise(function (resolve, reject) {});
    
        return Promise;
    }
    
    getPromise().then(function(post) {
        return getJSON(post.commentURL);
    }).then(function(comments) {
        // some code
    }).catch(function(error) {
        // 处理前面三个Promise产生的错误:无论是 getJSON产生错误,还是 then 产生的错误,都会由 catch 捕捉到
    });
    
    • 最佳实践: 一般来说,不要在then方法里面定义 reject 状态的回调函数
    // bad
    promise
      .then(function(data) {
        // success
      }, function(err) {
        // error
      });
    
    // good
    promise
      .then(function(data) {
        // success
      })
      .catch(function(err) {
        // error
      });
    

    3. finally 方法

    • 作用: 为 Promise 实例添加 回调函数(不管状态是 resolvereject),该回调函数都执行

    • 语法: promise.finally(() => {}) 不接受任何参数

    • 举例应用:服务器使用 Promise 处理请求,然后使用 finally 方法关掉服务器

    promise
        .then(result => {···})
        .catch(error => {···})
        .finally(() => {···});
    

    4. thencatchfinally 回调的执行顺序

    • 以下原则 适用于: new Promise(...)Promise.resolve(...)Promise.reject(...)

      • 回调函数如果是 同步函数: 会在本轮事件循环的末尾执行(下一轮循环之前)

      • 回调函数如果是 异步函数: 会在本轮事件循环的末尾 将 该异步函数放入到事件循环队列中,等待时间为 0s (也就是在下一轮循环的最后执行)

    • 示例如下:

    // 回调函数 是 同步函数
    
    setTimeout(function () {
        console.log(3);
    }, 0);
    
    const promise = new Promise(function (resolve, reject) {
        console.log(6);
        resolve();
    });
    
    setTimeout(function () {
        console.log(4);
    }, 0);
    
    promise.then(() => {
        console.log(2);
    });
    
    setTimeout(function () {
        console.log(5);
    }, 0);
    
    console.log(1);
    
    // 6 1 2 3 4 5
    
    // 回调函数 是 异步函数
    
    setTimeout(function () {
        console.log(3);
    }, 0);
    
    const promise = new Promise(function (resolve, reject) {
        console.log(6);
        resolve();
    });
    
    setTimeout(function () {
        console.log(4);
    }, 0);
    
    promise.then(() => {
        setTimeout(() => {
            console.log(2);
        }, 0);
    });
    
    setTimeout(function () {
        console.log(5);
    }, 0);
    
    console.log(1);
    
    // 6 1 3 4 5 2
    

    5. 回顾:JS 的异常捕获

    • 作用: 对于可能会出现错误的代码,使用异常捕获方式,可以使 JS 运行到错误代码处,不终止代码执行 且 抛出异常错误

    • 三种最佳实践:

      • try...catch

      • try...finally

      • try...catch...finally

    • 详解:

      • try 语句: 包含 可能出现错误的代码

      • catch 语句: 包含 捕捉到错误,执行的代码

      • finally 语句: 包含 无论有没有错误,都要执行的代码

    • 示例:

    try {
        console.log(11);    // 可能会发生错误的代码
    }
    catch (e) {
       throw e;     // 抛出错误异常,且 catch 代码块中 throw 之后的代码 不会执行
    }
    finally {
        
    }
    

    五、多个异步 都 执行完成后,再执行回调:Promise.all()

    • 作用: 将多个异步函数 包装成一个 内部并行执行的 Promise 实例

    • 关键: 包装后的 Promise 实例,内部函数 并行执行;减少了执行时间

    • 语法: const promise = Promise.all([p1, p2, p3])

      • p1、p2、p3都是 Promise 实例;如果不是Promise 实例,就会先调用 Promise.resolve 方法,将参数转为 Promise 实例

      • 参数可以不是数组,但 必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例

    • 使用场景: 多个异步 都 执行完成后,再执行回调

    Promise
      .all([runAsync1(), runAsync2(), runAsync3()])
      .then((results) => {
          console.log(results);
      });
      
    // 如上:三个异步操作 会 并行执行,等到它们都执行完后,才会执行 then 里面的代码;
    // 三个异步操作的结果,组成数组 传给了 then; 也就是 输出上述 result 为 [异步1数据,异步2数据,异步3数据]
    
    • 包装后的 Promise 的状态(区别所在)

      • 只有p1、p2、p3的状态都变成 fulfilled,p的状态才会变成 fulfilled;此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数

      • 只要p1、p2、p3之中有一个被 rejected,p的状态就变成rejected;此时第一个被 reject 的实例的返回值,会传递给p的回调函数

    • Promise 的状态 决定执行 then 回调,还是执行 catch 回调

    const p1 = new Promise((resolve, reject) => {
            resolve('hello');
        })
        .then(result => result)
        .catch(e => e);
    
    const p2 = new Promise((resolve, reject) => {
            throw new Error('报错了');
        })
        .then(result => result);
    
    Promise.all([p1, p2])
        .then((result) => {
            console.log('then');
            console.log(result)
        })
        .catch((e) => {
            console.log('error')
            console.log(e);
        });
    
    // 'error', Error: 报错了
    
    const p1 = new Promise((resolve, reject) => {
            resolve('hello');
        })
        .then(result => result)
        .catch(e => e);
    
    const p2 = new Promise((resolve, reject) => {
            throw new Error('报错了');
        })
        .then(result => result)
        .catch(e => e);
    
    Promise.all([p1, p2])
        .then((result) => {
            console.log('then');
            console.log(result)
        })
        .catch((e) => {
            console.log('error')
            console.log(e);
        });
    
    // 'then', ["hello", Error: 报错了]
    // 解释:因为 p2 有自己的 catch, 错误才 p2 处被拦截
    

    六、多个异步 中 有一个完成,就执行回调:Promise.race()

    • 作用: 将多个异步函数 包装成一个 内部并行执行的 Promise 实例

    • 关键: 包装后的 Promise 实例,内部函数 并行执行;减少了执行时间

    • 语法: const promise = Promise.race([p1, p2, p3])

      • p1、p2、p3都是 Promise 实例;如果不是Promise 实例,(如果是函数)就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例

      • 参数可以不是数组,但 必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例

    • 使用场景: 多个异步 中 有一个完成,就执行回调

    Promise
      .race([runAsync1(), runAsync2(), runAsync3()])
      .then((results) => {
          console.log(results);
      });
      
    // 如上:三个异步函数会并行执行,只要有一个执行完,就会执行 then;
    // 率先执行完的异步函数的结果 作为参数 传递给 then 中的回调函数
    
    // 请求超时的 demo
    
    const p1 = myAjax('/resource-that-may-take-a-while');
    const p2 = new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('request timeout')), 5000)
        });
    
    Promise.race([p1, p2])
        .then(response => console.log(response))
        .catch(error => console.log(error));
    
    • 包装后的 Promise 的状态(区别所在)
      • p1、p2、p3 中 率先 改变状态的 Promise 的状态值,直接影响 包装后的Promise 的状态;
      • 那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

    六、快速生成 Promise 对象 的方法

    1. Promise.resolve()

    • 作用: 将现有对象转为 Promise 对象(状态为 resolve

    • 等价于:

    Promise.resolve('foo');
    
    // 等价于
    
    new Promise((resolve, reject) => {
        resolve('foo');
    });
    
    • 参数的几种形式:
      • 参数是 具有then方法的对象: 转为 Promise 对象,并立即执行对象的 then 方法 【重点区分】

      • 参数是 Promise 实例: 那么Promise.resolve将不做任何修改、原封不动地返回这个实例

      • 不带有任何参数: 最快速生成 Promise 对象

    // 参数是 具有then方法的对象
    
    let thenable = {
        then: function (resolve, reject) {
            console.log('then');
            resolve(42);
        }
    };
    
    Promise.resolve(thenable);  //  输出 then
    
    // 不带参数,快速生成 Promise 对象
    
    Promise.resolve();
    

    2. Promise.reject()

    • 作用: 返回一个新的 Promise 实例,该实例的状态为 rejected

    • 等价于:

    Promise.reject('出错了');
    
    // 等价于
    
    new Promise((resolve, reject) => {
        reject('报错了');
    });
    
    • 参数: 无论参数是什么都会原封不动的作为 错误理由 【重点区分】

    七、Promise 的应用

    • Promise 实现 异步加载图片
    // Promise 实现 单张图片 异步加载(预加载)
    
    function loadImageAsync(url) {
        return new Promise(function (resolve, reject) {
            const image = new Image();
    
            image.onload = function () {
                resolve(image);
            };
    
            image.onerror = function () {
                reject(new Error('Could not load image at ' + url));
            };
    
            image.src = url;
        });
    }
    
    loadImageAsync('./1png')
        .then((img) => {
            console.log(img); // 图片标签
        })
    
    // Promise 实现 多张图片 异步加载(预加载)
    
    function loadImageAsync(url) {
        return new Promise(function (resolve, reject) {
            const image = new Image();
    
            image.onload = function () {
                resolve(image);
            };
    
            image.onerror = function () {
                reject(new Error('Could not load image at ' + url));
            };
    
            image.src = url;
        });
    }
    
    function getImages(source) {
        const imgs = [];
    
        source.forEach(url => {
            loadImageAsync(url)
                .then((img) => {
                    imgs.push(img);
                })
        });
    
        return imgs;
    }
    
    console.log(getImages(['1.png', '2.png']));
    
    // ES5 实现图片异步加载
    
    function getAsyncLoadImages(source) {
        const imgs = [];
    
        source.forEach(function (url, i) {
            imgs[i] = new Image();
            imgs[i].onload = function () {
                // console.log(imgs[i]);
            }
    
            imgs[i].onerror = function () {
                reject(new Error('Could not load image at ' + url));
            };
    
            imgs[i].src = url;
        })
    
        return imgs;
    }
    
    const result = getAsyncLoadImages(['1.png', '2.png']);
    console.log(result);
    
  • 相关阅读:
    Ynoi 杂题选做
    CSP-S2021 浙江 游记
    P6272 [湖北省队互测2014]没有人的算术
    P5206 [WC2019]数树
    P5405 [CTS2019]氪金手游
    LOJ6679 Unknow
    LOJ3040 「JOISC 2019 Day4」合并
    P6122 [NEERC2016]Mole Tunnels
    三维凸包
    三,四元环计数
  • 原文地址:https://www.cnblogs.com/zxvictory/p/8461725.html
Copyright © 2011-2022 走看看