• 原来你是这样的Promise


    1. Promise简介

    promise是异步编程的一种解决方案,它出现的初衷是为了解决回调地狱的问题。

    打个比方,我需要:

    --(延迟1s)--> 输出1 --(延迟2s)--> 输出2 --(延迟3s)--> 输出3

    通常写法:

    setTimeout(()=> {
        console.log('1');
        setTimeout(()=> {
            console.log('2');
            setTimeout(()=> {
                console.log('3'); 
            }, 3000)
        }, 2000)
    }, 1000)

    这样的多重的嵌套的回调被称为回调地狱,这样的代码可读性很差,不利于理解。

    如果用promise的话画风一转

    function delay(time, num) {
        return new Promise((res, rej)=> {
            setTimeout(()=> {
                console.log(num);
                res();
            }, time*1000)
        });
    }
    delay(1, 1).then(()=> {
        return delay(2, 2);
    }).then(()=> {
        delay(3, 3);
    })
    使用了promise的链式调用,代码结构更清晰。

    是不是很棒?那还不赶快get起来~

    2. Promise的使用

    调用方式如下:

    new Promise((resolve, reject)=> {
        if('some option') {
            resolve('some value');
        } else {
            reject('some error');
        }
    }).then(
        val=> {
            // ...
        },
        error=> {
            // ...
        }
    )

    Promise构造函数接收一个函数型参数fn,fn有两个参数,分别是:resolve、reject,Promise还有一个Promise.prototype.then方法,该方法接收两个参数,分别是成功的回调函数succ和失败的回调函数error。

    在fn中调用resolve会触发then中的succ回调,调用reject会触发error回调。

    2.1 参数传递

    • 在fn内部调用resolve/reject传入的参数会作为相应参数传入相应的回调函数
      new Promise((res, rej)=> {
          res('happy')
      }).then(val=> {
          console.log(val);  // happy
      });
      
      new Promise((res, rej)=> {
          rej('error!');
      }).then(val=> {}, err=> {
          console.log(err);  // error!
      });
    • 链式调用时若上一级没有传递值则默认为undefined
      new Promise((res, rej)=> {
          res('a');    
      }).then(val=> {
          return 'b'
      }).then(val=> {
          console.log(val);  // 'b'
      }).then((val)=> {
          console.log(val);  // 'undefined'
      });
    • 若上一级的then中传递的并非函数,则忽略该级
      new Promise((res, rej)=> {
          res('a');    
      }).then(val=> {
          return 'b';
      }).then(val=> {
          console.log(val);  // 'b'
          return 'c';
      }).then({  // 并非函数
          name: 'lan'
      }).then((val)=> {
          console.log(val);   // 'c'
      });

    2.2 参数传递例题

    let doSomething = function() {
        return new Promise((resolve, reject) => {
            resolve('返回值');
        });
    };
    
    let doSomethingElse = function() {
        return '新的值';
    }
    
    doSomething().then(function () {
        return doSomethingElse();
    }).then(resp => {
        console.warn(resp);
        console.warn('1 =========<');
    });
    
    doSomething().then(function () {
        doSomethingElse();
    }).then(resp => {
        console.warn(resp);
        console.warn('2 =========<');
    });
    
    doSomething().then(doSomethingElse()).then(resp => {
        console.warn(resp);
        console.warn('3 =========<');
    });
    
    doSomething().then(doSomethingElse).then(resp => {
        console.warn(resp);
        console.warn('4 =========<');
    });

    结合上面的讲解想一想会输出什么?(答案及解析

    3. Promise.prototype.then

    当Promise中的状态(pending ---> resolved or rejected)发生变化时才会执行then方法。

    • 调用then返回的依旧是一个Promise实例 ( 所以才可以链式调用... )
    new Promise((res, rej)=> {
        res('a');
    }).then(val=> {
        return 'b';
    });
    
    // 等同于
    new Promise((res, rej)=> {
        res('a');
    }).then(val=> {
        return new Promise((res, rej)=> {
            res('b');
        });
    });
    • then中的回调总会异步执行
    new Promise((res, rej)=> {
        console.log('a');
        res('');
    }).then(()=> {
        console.log('b');
    });
    console.log('c');
    // a c b
    • 如果你不在Promise的参数函数中调用resolve或者reject那么then方法永远不会被触发
    new Promise((res, rej)=> {
        console.log('a'); 
    }).then(()=> {
        console.log('b');
    });
    console.log('c'); 
    // a c

    4. Promise的静态方法

    Promise还有四个静态方法,分别是resolve、reject、all、race,下面我们一一介绍一下。

    4.1 Promise.resolve()

    除了通过new Promise()的方式,我们还有两种创建Promise对象的方法,Promise.resolve()相当于创建了一个立即resolve的对象。如下两段代码作用相同:

    Promise.resolve('a');
    
    new Promise((res, rej)=> {
        res('a');
    });

    当然根据传入的参数不同,Promise.resolve()也会做出不同的操作。

    • 参数是一个 Promise 实例

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

    • 参数是一个thenable对象

    thenable对象指的是具有then方法的对象,比如下面这个对象。

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };

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

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

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

    • 不带有任何参数

    Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

    值得注意的一点是该静态方法是在本次事件轮询结束前调用,而不是在下一次事件轮询开始时调用。关于事件轮询可以看这里——>JavaScript 运行机制详解:再谈Event Loop

    4.2 Promise.reject()

    和Promise.resolve()类似,只不过一个是触发成功的回调,一个是触发失败的回调

    4.3 Promise.all()

    Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

    function asyncFun1() {
        return new Promise((res, rej)=> {
            setTimeout(()=> { 
                res('a');
            }, 1000);
        }); 
    }
    function asyncFun2() {
        return new Promise((res, rej)=> {
            setTimeout(()=> { 
                res('b');
            }, 1000);
        }); 
    }
    function asyncFun3() {
        return new Promise((res, rej)=> {
            setTimeout(()=> { 
                res('c');
            }, 1000);
        }); 
    }
    Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
        console.log(val);
    });
    Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
        console.log(val);  // ['a', 'b', 'c']
    });

    用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

    适用场景:打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

    4.4 Promise.race()

    race()和all相反,all()是数组中所有Promise都执行完毕就执行then,而race()是一旦有一个Promise执行完毕就会执行then(),用上面的三个Promise返回值函数举例

    Promise.race([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
        console.log(val);  // a
    });

    5. 链式调用经典例题

    看了这么多关于Promise的知识,我们来做一道题巩固一下。

    写一个类Man实现以下链式调用
    
    调用方式:
    new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
    打印:
    'hello, lan' -(等待3s)--> 'lan eat apple' -(等待5s)--> 'lan eat banana'

    思路:

    • 在原型方法中返回this达到链式调用的目的
    • 等待3s执行的效果可以用Promise & then实现

    具体实现如下:

    class Man {
        constructor(name) {
            this.name = name;
            this.sayName();
            this.rope = Promise.resolve();  // 定义全局Promise作链式调用
        }
        sayName() {
            console.log(`hello, ${this.name}`);
        }
        sleep(time) {
            this.rope = this.rope.then(()=> {
                return new Promise((res, rej)=> {
                    setTimeout(()=> {
                        res();
                    }, time*1000);
                });
            });
            return this;
        }
        eat(food) {
            this.rope = this.rope.then(()=> {
                console.log(`${this.name} eat ${food}`); 
            });
    
            return this;
        }
    }
    
    new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

    ok!不知道你有没有看懂呢?如果能完全理解代码那你的Promise可以通关了,顺便来个小思考,下面这种写法可以吗?和上面相比有什么区别?:

    class Man1345 {
        constructor(name) {
            this.name = name;
            this.sayName(); 
        }
        sayName() {
            console.log(`hello, ${this.name}`);
        }
        sleep(time) { 
            this.rope = new Promise((res, rej)=> {
                    setTimeout(()=> {
                        res();
                    }, time*1000);
                }); 
            return this;
        }
        eat(food) {
            this.rope = this.rope.then(()=> { 
                console.log(`${this.name} eat ${food}`);  
            });
    
            return this;
        }
    }
    
    new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

    简单的说,第二段代码的执行结果是

    'hello, lan' -(等待3s)--> 'lan eat apple' ---> 'lan eat banana'

    为什么会出现这种差别? 因为第二段代码每一次调用sleep都会new一个新的Promise对象,调用了两次sleep就new了两个Promise对象。这两个对象是异步并行执行,会造成两句eat同时显示。

    和以下情况类似

    var time1 = setTimeout(()=> {
        console.log('a');
    }, 1000)
    var time2 = setTimeout(()=> {
        console.log('b');
    }, 1000)
    // 同时输出 a b

    抽象一点的讲解是:

    // 第一段正确的代码的执行为
    var p1 = new Promise().then('停顿3s').then('打印食物').then('停顿5s').then('打印食物');
    
    // 第二段代码的执行行为,p1、p2异步并行执行
    var p1 = new Promise().then('停顿3s').then('打印食物');
    var p2 = new Promise().then('停顿5s').then('打印食物');
    总结

    Promise的经常用到的地方:

    • 摆脱回调地狱
    • 多个异步任务同步

    Promise是我们的好帮手,不过还有另一种方法也可以做到,那就是async&await,可以多多了解一下。

    参考资料

    ECMAScript 6 入门

    通俗浅显的理解Promise中的then

    大白话讲解promise

  • 相关阅读:
    Java参数传递方式
    C++成员函数的 重载、隐藏、覆盖分析(转)
    回调函数 (一)
    Java之String 专题二
    从10亿个浮点数中找出最大的1万个
    【onclick事件】【改变 HTML 内容innerHTML】【图片替换】【改变标签的css】【判断输入是否是数字】
    【页面加载】【九九乘法表】【document.write的功能_】【<script>直接显示数组】【声明新变量】
    Windows10 环境下安装 ElasticSearch
    数据包和数据报有何区别?
    NIO 通道和缓冲区
  • 原文地址:https://www.cnblogs.com/wind-lanyan/p/8849643.html
Copyright © 2020-2023  润新知