美文网首页
Promise基础用法

Promise基础用法

作者: 风雪中的兔子 | 来源:发表于2019-03-28 07:27 被阅读0次

    > 简述:

    ## 什么是Promise?

    - Promise是用来处理异步的;

    - Promise就是承诺,对未来的承诺;

    - 所谓的Promise(承诺),里面保存着未来才会结束的事件的结果;

    - Promise是异步编程的一种解决方案; 比传统的解决方案(回调函数和事件)更合理更强大;

    - Promise一个对象,从它可以获取异步操作的消息;

    - Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了

    ### 两个特点

    - 对象的状态不受外界影响, Promise对象代表一个异步操作;

    #### 3种状态

        1 pending     等待态

        2 fulfilled 已成功

        3 rejected  已失败

        只有异步操作的结果,可以决定当前是哪一个状态,任何外部的操作都无法改变这个状态

    - 状态一旦改变,就不会也不能再变,任何时候都可以得到这个结果;

    Promise的状态,只能从pending变为fulfilled或rejected, fulfilled和rejected不能相互转换;

    ## 诞生的时代背景 ##

    > 在JavaScript的世界中,所有代码都是单线程执行的; 由于这个"缺陷",以至于JavaScript的所有网络操作、浏览器事件等都必须异步执行; 异步意味着在未来的某个时刻得到结果; 在promise之前,我们写的ajax应用(前一个函数的执行结果作为下一个函数的参数),就面临着大量的回调函数的窘境;

    ## 为解决回调而生 ##

    - Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数;

    - Promise对象提供统一的接口,使得控制异步更加容易;

    ## 不是最perfect, 因为它还是基于回调 ##

    - 无法取消Promise,一旦新建就会立即执行,无法中途取消;

    - 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;

    - 当处于pending时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

    - 从本质上来说,Promise还是基于回调的

    ## 基础用法 ##

    ### 一、构造函数与then ###

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

    - Promise接收一个函数(executor:执行者)作为参数,该函数有两个参数:

        1 resolve(Function)      -> 将Promise对象的状态从"未完成pending"变为"成功resolved",在异步操作成时调用,并将异步操作的结果,作为参数传递出去(供then方法的成功回调接收)

        2 reject(Function)        -> 将Promise对象的状态从"未完成pending"变为"rejected",在异步失败时调用,并将异步操作的错误或失败的原因,作为参数传递出去(供then方法的失败回调接收)

    ```

    const promise = new Promise(function(resolve, reject){

        if(/*异步操作成*/){

            resolve(value); // 成功时调用它会将Promise的状态从"pending"变为"resolved"

        }else{

            reject(err);    // 失败时调用它会将Promise的状态从"pending"变为"rejected"

        }

    });

    ```

    - Promise实例生成后,可以用then方法分别指定resolved(参数是成功的结果)状态和rejected(参数是失败的原因)状态的回调函数; 即then方法接收两个回调函数作为参数:

        1 第一个函数,是Promise对象的状态变为resolved时调用(接收成功的结果作为参数)

        2 第二个函数,是Promise对象的状态变为rejected时调用[可选](接收失败的原因或错误作为参数)

    ```

    promise.then((value)=>{

        // value是成功的结果

    }, (err)=>{

        // 失败的原因

    });

    // 示例: demo/1.创建promise实例.js

    ```

    - Promise的状态发生改变时就会触发then方法对应的不同状态的函数参数

    ### 二、Promise新建后会立即执行 ###

    - Promise是同步的,promise.then方法是异步的

    ```

    const promise = new Promise((resolve, reject)=>{

        console.log('Promise');

        resolve();

    });

    promise.then((value)=>{

        console.log('Success')

    });

    console.log('Hi!');

    // => Promie  Hi! Success

    // 上面代码,Promise新建后会立即执行,所以会先输出'Promise', 然后我们为then方法指定了回调函数,因为then方法是异步的,所以它会等待同步代码都执行完之后才执行,所以接下来输出的是'Hi!',当主栈中的同步代码执行完毕之后, 开始执行异步任务(then是个微任务), 因此promise状态改变后成功的回调输出'Success'

    // 示例: demo/2.promise创建会立即执行

    ```

    ```

    // 异步加载图片示例

    function loadImageAsync(url){

        return new Promise((resolve, reject) => {

            let image = new Image();

            image.onload = function(){

                resolve(image);  // 如果图片加载成则调用该方法, 将promise的状态改为resolved

            };

            image.onerror = function(){

                reject('创建失败'); // 如果图片加载失败, 则调用该方法, 将promise的状态改为rejected

            };

            image.src = url;

        });

    }

    loadImageAsync('./timg2.jpg').then((value)=>{

        // 如果状态变为成功态,则将图片添加到页面

        console.log(value);

        document.querySelector('body').appendChild(value);

    }, (err)=>{

        // 状态变为失败态,则将提示错误

        console.log('Error:', err);

    });

    // 示例: demo/3.异步加载图片

    ```

    ```

    // Promise对象实现Ajax示例

    let getJSON = function(){

        return new Promise((resolve, reject)=>{

            $.getJSON('./test.json').then((value)=>{

                console.log(value);

                resolve(value)

            }, (err)=>{

                // console.log('失败回调:',err)

                reject(err);

            });  

        });

    };

    getJSON().then((data)=>{

        console.log(data)

    },(err)=>{

        console.log('My-Error:', err);

    });

    // 如果调用resolve函数和reject函数时带有参数,那么它们的参数被被传递给回调函数(then的成功回调和失败回调)

    // 示例: demo/4.Promise对象实现Ajax

    ```

    - Promise的回调函数中抛出错误或异常,则直接调用then的失败态(reject)回调函数

    ```

    new Promise((resolve,reject)=>{

        throw new Error('错误');

    }).then((data)=>{

        console.log(data)

    },(err) => {

        console.log('Err ', err);  //Err  Error: 错误....

        return err;

    })

    ```

    ### 三、resolve函数返回Promise实例 ###

    > resolve函数的参数除了正常的值以外,还能是另一个Promise实例

    ```

    const p1 = new Promise(function (resolve, reject) {

      // ...

    });

    const p2 = new Promise(function (resolve, reject) {

      // ...

      resolve(p1);

    })

    上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作

    注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行

    ```

    ```

    const p1 = new Promise(function (resolve, reject) {

      setTimeout(() => reject(new Error('fail')), 3000)

    })

    const p2 = new Promise(function (resolve, reject) {

      setTimeout(() => resolve(p1), 1000)

    })

    p2

      .then(result => console.log(result))

      .catch(error => console.log(error))

    // Error: fail

    //上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数

    // 一言以蔽之: "传递的promise都要执行,直到执行后的结果是普通值(非promise)或抛出异常, 再传递到下一个then中, 执行对应的回调"

    ```

    ### 四、调用resolve与reject并不会终结Promise的参数函数(executor)执行 ###

    ```

    new Promise((resolve,reject)=>{

        resolve('success');

        console.log('Hello')

    }).then(data=>{

        console.log(data)

    });

    // => 'Hello' 'success'

    // 一般来说, 调用resolve和reject以后,Promise的使命就完成了,后续的操作应该放到then方法里面,而不应该直接写在resolve和reject的后面; 所以,最好在它们前面加上return语句;

    ```

    ```

    new Promise((resolve,reject)=>{

        return resolve('success');

        console.log('Hello')

    }).then(data=>{

        console.log(data)

    });

    // 此时只输出success

    ```

    ### 五、Promise.prototype.then() ###

    - Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的;

    - then方法的作用是为Promise实例添加状态改变时的回调函数,它的第一个参数是resolve状态的回调函数,第二个参数是rejected状态的回调函数;

    - then方法返回的是一个新的Promise实例(注意不是原来那个Promise实例);

    - 链式写法,即then方法后面再调用另一个then方法

    1、 then链式调用, 第一个then回调的返回结果会作为第二个then回调的参数

    ```

    new Promise((resolve,reject)=>{

        return resolve('success');       // 将状态改变为成功态

    }).then((data)=>{

        console.log(data)                // success

        return '成功' + data;

    },(err) => {

        console.log('Err', err);

        return err;

    }).then((data) => {

        console.log(data)                // 成功success

    }, (err) =>{

        console.log("Fail", err)

    });

    // 上面代码使用then方法, 依次执行两个then的回调函数. 第一个回调完成后,会将返回结果作为参数,传入到第二个回调函数;

    ```

    2、 then的reject回调函数中返回错误,会继续传递到下一个then的成功(resolve)回调**`切记`**

    ```

    new Promise((resolve,reject)=>{

        return reject('fail');  // 执行失败回调

    }).then((data)=>{

        console.log(data);         

        return '成功';

    },(err) => {

        console.log('Err', err);  // Err fail

        return err;

    }).then((data) => {

        console.log('hello',data) // hello fail

    }, (err) =>{

        console.log("Fail", err)

    });

    ```

    3、then()会多层传递

    ```

    new Promise((resolve,reject)=>{

        resolve('ABC');

    }).then().then().then((data)=>{

        console.log(data)

    },(err)=>{

        console.log(err)

    });

    // 输出 "ABC"

    ```

    ### 六、Promise.prototype.catch() ###

    - Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数

    ```

    new Promise((resolve,reject)=>{

        throw new Error('错误');

    }).catch((err)=>{

        console.log('Error:', err); // Error: Error: 错误

    });

    等同于

    new Promise((resolve,reject)=>{

        throw new Error('错误');

    }).then(null, (err)=>{

        console.log('Error:', err); // Error: Error: 错误

    });

    ```

    - 在Promise的回调函数中直接抛出错误,此时状态变为rejected, 错误会被catch捕获

    ```

    new Promise((resolve,reject)=>{

        throw new Error('错误');

    }).catch((err)=>{

        console.log('Error:', err); // Error: Error: 错误

    });

    ```

    - then方法指定回调函数, 在运行中抛出错误, 也会被catch捕获

    ```

    new Promise((resolve,reject)=>{

        resolve('ABC');       // 将状态改变为成功态

    }).then((data)=>{

        console.log(test)    // 在then的成功回调中执行,输出一个未定义的变量,此时会抛出错误

    },(err) => {

        console.log('Err ', err);

    }).catch((err)=>{

        console.log('Catch ', err); // 在这里被捕获 => Catch  ReferenceError: test is not defined ....

    });

    ```

    - 如果Promise的状态已经变成resolved, 再抛出错误是无效的

    ```

    new Promise((resolve,reject)=>{

        resolve('ABC');

        throw new Error('错误');

    }).then((data)=>{

        console.log(data)  // 输出ABC

    },(err) => {

        console.log('Err ', err);

    }).catch((err)=>{

        console.log('Catch ', err);

    });

    // 上面代码执行输出了"ABC",说明在状态改变后抛出错误,对结果无影响

    // 上面代码中,Promise在resolve语句后面抛出错误,没有被捕获,相当于没有抛出错误;因为Promise的状态一旦改变,就永远保持该状态,不会再变了;

    ```

    - reject的作用等同于抛出错误

    ```

    new Promise((resolve,reject)=>{

        reject('ABC');

    }).catch((err)=>{

        console.log('Catch ', err);

    });

    // 输出: "Catch  ABC"

    等同于

    new Promise((resolve,reject)=>{

        try{

            console.log('a')

            throw new Error('test');

        }catch(e){

            console.log('b')

            reject(e)

        }

    }).catch((err)=>{

        console.log('Catch ', err);

    });

    // => a b Catch  Error: test

    ```

    ### Promise.all() ###

    - Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例

    - Promise.all方法接受一个数组作为参数(数组中的每一项都是一个promise实例),只有所有的实例状态都变为fulfilled, promise实例的状态才会变成fulfilled, 此时数组中promise实例的返回值会组成一个数组,传递给该promise实例的回调函数, 返回值在数组中的顺序与promise数组中promise实例的顺序一致

    ```

    let p1 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(100);

        }, 2000);

    });

    let p2 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve('Hello');

        }, 3000);

    });

    let p3 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(true);

        }, 2000);

    });

    Promise.all([p1, p2, p3]).then((data)=>{

        console.log(data);  // 等数组中的promise实例都执行完成后,才将状态改变为fulfilled => [ 100, 'Hello', true ]

    }, (err)=>{

        console.log(err)

    });

    // => [ 100, 'Hello', true ]

    ```

    - 如果其中有一个promise实例抛出错误,就会走到Promise.all().catch()方法

    ```

    let p1 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(100);

        }, 2000);

    });

    let p2 = new Promise((resolve,reject)=>{

        throw Error('错误啦');

    });

    let p3 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(true);

        }, 2000);

    });

    Promise.all([p1, p2, p3]).then((data)=>{

        console.log(data)

    }).catch(err=>{

        console.log('Promise.all Error:', err)

    });

    // => Promise.all Error: Error: 错误啦....

    // 在p2执行时抛出了异常,那么整个Promise.all()执行状态被改变为rejected, 错误被catch捕获

    ```

    - 如果作为参数的Promise实例,自己定义了catch方法, 那么一旦被rejected,并不会触发Promise.all()的catch方法

    ```

    let p1 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(100);

        }, 2000);

    });

    let p2 = new Promise((resolve,reject)=>{

        throw Error('错误啦');

    }).catch(err=>{

        console.log('p2.Error', err);        // p2.Error Error: 错误啦

    });

    Promise.all([p1, p2]).then((data)=>{

        console.log(data);                    // [ 100, undefined ]

    }).catch(err=>{

        console.log('Promise.all Error:', err)

    });

    // 上例中,p1在2秒后状态改变为resolve; p2执行时抛出错误,此时p2的状态改变为rejected, 因为它有自己的catch,在执行完catch方法后,状态也变成了resolve, 因此会调用then的指定的回调, 而不会调用catch方法指定的回调函数; 所以,最终打印出: [ 100, undefined ]

    ```

    ### Promise.race() ###

    - Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例

    - 同上面all()方法相比,只要有一个实例率先改变状态,整个promise实例的状态就跟随改变; 与此同时,第一个改变的promise实例(数组中的)的值,被传递给Promise的回调函数

    ```

    let p1 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(100);

        }, 2000);

    });

    let p3 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(true);

        }, 2500);

    });

    Promise.race([p1,  p3]).then((data)=>{

        console.log(data)        // 100

    }).catch(err=>{

        console.log('Promise.all Error:', err)

    });

    ```

    - 如果promise数组中有一个普通值,会立马返回

    ```

    let p1 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(100);

        }, 2000);

    });

    let p3 = new Promise((resolve,reject)=>{

        setTimeout(() => {

            resolve(true);

        }, 2500);

    });

    Promise.race([p1,  p3, 10086]).then((data)=>{

        console.log(data)    // 10086

    }).catch(err=>{

        console.log('Promise.all Error:', err)

    });

    // => 10086; 因为我们最终要使用的是普通值或普通对象,而非promsie,所有的实例最终的目的都是要返回普通值, 因此遇到普通值会立马返回

    ```

    ### Promise.resolve() ###

    - Promise.resolve()可用于将现有对象转换为Promise对象

    - `Promise.resolve('foo')`等价于`new Promise(resolve=>resolve('foo'))`

    - Promise.resolve方法的参数分成四种情况:

        1、参数是一个 Promise 实例

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

        2、参数是一个thenable对象(thenable对象指的是具有then方法的对象)

         - Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法

         ```

          let thenable = {

            then: function(resolve, reject) {

              resolve(42);

            }

          };

          let p1 = Promise.resolve(thenable);

          p1.then(function(value) {

            console.log(value);  // 42

          });

          // thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42    

         ```

    3、参数不是具有then方法的对象,或根本就不是对象

    - 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise对象,状态为resolved

        ```

        const p = Promise.resolve('Hello');

        p.then(function (s){

          console.log(s)

        });

        // Hello

        // 上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数

        ```

        4、不带有任何参数

    - Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的Promise对象,所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法

    ### Promise.reject() ###

    - Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

    ```

    const p = Promise.reject('出错了');

    // 等同于

    const p = new Promise((resolve, reject) => reject('出错了'))

    p.then(null, function (s) {

      console.log(s)

    });

    // 出错了

    //上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行

    ```

    - Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数.这一点与Promise.resolve方法不一致

    ```

    const thenable = {

      then(resolve, reject) {

        reject('出错了');

      }

    };

    Promise.reject(thenable)

    .catch(e => {

      console.log(e === thenable)

    })

    // true

    // 上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的"出错了"这个字符串,而是thenable对象

    ```

    ### 参考文档 ###

    [ECMAScript 6 入门 - Promise对象](http://es6.ruanyifeng.com/#docs/promise)

    相关文章

      网友评论

          本文标题:Promise基础用法

          本文链接:https://www.haomeiwen.com/subject/oqodkqtx.html