zoukankan      html  css  js  c++  java
  • 深入浅出:promise的各种用法

    https://mp.weixin.qq.com/s?__biz=MzAwNTAzMjcxNg==&mid=2651425195&idx=1&sn=eed6bea35323c75f0c43ae61818c0a55&chksm=80dff7c8b7a87edeb834cc4aabf0eec40c7566b45abd5c58b56625dc0efd77d15c9e64534140&mpshare=1&scene=1&srcid=02260hYIB6d5lSLVwyvPIUWX#rd 

    Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。 

    var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
    console.log('执行完成');
    resolve('随便什么数据');
    }, 2000);
    });

    二、使用promise的好处是:
    1.代码结构更加扁平且更可读,清晰明了。

    2.能解决回调地狱问题。

    3.可将数据请求和业务逻辑分离开来。

    4.便于管理维护。

    5.能更好的捕获错误。

    1.优点和缺点
    可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
     
    Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 

    2.Promise规范如下:

    一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
    一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
    promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
    then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。

    3.promise的特性:

    (1.)立即执行性 

    var p=new Promise(function(resolve,reject)(){
    console.log("create new promise");
    resolve("success");
    });
     
    console.log("after new  promise");
     
    p.then(function(value){
    console.log(value);
    });
    //create new promise
    //after new  promise
    //success
     

    (2.)状态不可逆,链式调用

    var p = new Promise(function(resolve, reject){
      resolve(1);
    });
    p.then(function(value){               //第一个then
      console.log(value);
      return value*2;
    }).then(function(value){              //第二个then
      console.log(value);
    }).then(function(value){              //第三个then
      console.log(value);
      return Promise.resolve('resolve'); 
    }).then(function(value){              //第四个then
      console.log(value);
      return Promise.reject('reject');
    }).then(function(value){              //第五个then
      console.log('resolve: '+ value);
    }, function(err){
      console.log('reject: ' + err);
    })
    //1
    //2
    //undefined
    //resolve
    //reject: reject

     (3.)回调异步性

    var p = new Promise(function(resolve, reject){
      resolve("success");
    });
     
    p.then(function(value){
      console.log(value);
    });
     
    console.log("first");
    //"first"
    //"success"
     

    4.用法

    function getURL(URL) {
          return new Promise(function (resolve, reject) {
              var req = new XMLHttpRequest();
              req.open('GET', URL, true);
              req.onload = function () {
                  if (req.status === 200) {
                      resolve(req.responseText);
                  } else {
                      reject(new Error(req.statusText));
                  }
              };
              req.onerror = function () {
                  reject(new Error(req.statusText));
              };
              req.send();
          });
    }
    // 运行示例
    var URL = "http://httpbin.org/get";
    getURL(URL).then(function onFulfilled(value){
      console.log(value);
    }).catch(function onRejected(error){
      console.error(error);
    });
     

     原文参考:https://blog.csdn.net/qq_29849641/article/details/54970328

     三、promise的回调地狱

    一般我们要在一个函数执行完之后执行另一个函数我们称之为callback‘回调’,简单的写一下:
    setTimeout(function () {
    left(function () {
    setTimeout(function () {
    left(function () {
    setTimeout(function () {
    left();
    }, 2000);
    });
    }, 2000);
    });
    }, 2000);
      以上代码就是传说中的回调地狱,如果有多层业务逻辑嵌套的话,不仅会使代码阅读困难,而且后面维护起来也是难点。
    之后在ES6,Promise就应运而生。

    四、promise的than用法

    var promise = new Promise(function(resolve, reject) {
    // ... some code
    if (/* 异步操作成功 */){
    resolve(value);
    } else {
    reject(error);
    }
    });
       resolve(value)是在Promise在已经异步完成成功(Resolved)之后执行的
        reject(value)是在Promise在异步失败之后(Rejected)执行。
        当然,也可以用then来指定:then(resolve, reject)
        或者:then(resolve),catch (reject)
    promise.then(function(value) {
    // success
    }, function(error) {
    // failure
    });
    //等价于:
    promise.then(function(){
    //success
    }).catch(function(){
    //failure
    })
    总结:
    1.可以采用连续的then链式操作来写回调(这是因为返回值一直是新的Promise实例)。
    2.以上例子可以看出来只要在第一个promise回调中添加resolve,之后的连续then就会默认执行。
     
    可以在then中return出数据,并且这个数据会以参数的形式传入下一个then。
    var p = new Promise(function (resolve, reject) {
    var a = 1
    resolve(a);

    }).then(function (data) {
    console.log(data)
    return ++data;
    }).then(function (data) {
    console.log(data)
    })

    五、reject的用法

    reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。

    function getNumber() {
    var p = new Promise(function (resolve, reject) {
    //做一些异步操作
    setTimeout(function () {
    var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
    if (num <= 5) {
    resolve(num);
    }
    else {
    reject('数字太大了');
    }
    }, 2000);
    });
    return p;
    }

    getNumber()
    .then(
    function (data) {
    console.log('resolved');
    console.log(data);
    },
    function (reason, data) {
    console.log('rejected');
    console.log(reason);
    }
    );
    getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

    运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:

    六、catch的用法(Promise.prototype.catch())

    Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
    我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
    getNumber()
    .then(function (data) {
    console.log('resolved');
    console.log(data);
    })
    .catch(function (reason) {
    console.log('rejected');
    console.log(reason);
    });

    效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
    getNumber()
    .then(function (data) {
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
    })
    .catch(function (reason) {
    console.log('rejected');
    console.log(reason);
    });

      在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果: 

      也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

    七、all的用法

      Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子:

    Promise
    .all([runAsync1(), runAsync2(), runAsync3()])
    .then(function (results) {
    console.log(results);
    });
    用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:

      有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
    八、race的用法
      all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

    Promise
    .race([runAsync1(), runAsync2(), runAsync3()])
    .then(function (results) {
    console.log(results);
    });

    你猜对了吗?不完全,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。

    这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

    //请求某个图片资源
    function requestImg() {
    var p = new Promise(function (resolve, reject) {
    var img = new Image();
    img.onload = function () {
    resolve(img);
    }
    img.src = 'xxxxxx';
    });
    return p;
    }

    //延时函数,用于给请求计时
    function timeout() {
    var p = new Promise(function (resolve, reject) {
    setTimeout(function () {
    reject('图片请求超时');
    }, 5000);
    });
    return p;
    }

    Promise
    .race([requestImg(), timeout()])
    .then(function (results) {
    console.log(results);
    })
    .catch(function (reason) {
    console.log(reason);
    });

    requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
    九、Promise.prototype.finally()
    finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的
    promise
    .then(result => { ··· })
    .catch(error => { ··· })
    .finally(() => { ··· });
    上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
    下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

     pasting

    server.listen(port)
    .then(function () {
    // ...
    })
    .finally(server.stop);
    finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
    finally本质上是then方法的特例。
    promise
    .finally(() => {
    // 语句
    });

    // 等同于
    promise
    .then(
    result => {
    // 语句
    return result;
    },
    error => {
    // 语句
    throw error;
    }
    );
    上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
    它的实现也很简单。
    Promise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
    value => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
    );
    };
    上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
    从上面的实现还可以看到,finally方法总是会返回原来的值。
    // resolve 的值是 undefined
    Promise.resolve(2).then(() => { }, () => { })

    // resolve 的值是 2
    Promise.resolve(2).finally(() => { })

    // reject 的值是 undefined
    Promise.reject(3).then(() => { }, () => { })

    // reject 的值是 3
    Promise.reject(3).finally(() => { })
    十、Promise.try()
    实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
    Promise.resolve().then(f)
    上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
    const f = () => console.log('now');
    Promise.resolve().then(f);
    console.log('next');
    // next
    // now
    上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
    那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
    const f = () => console.log('now');
    (async () => f())();
    console.log('next');
    // now
    // next
    上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
    (async () => f())()
    .then(...)

     需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

     pasting

    (async () => f())()
    .then(...)
    .catch(...)
    第二种写法是使用new Promise()。
    const f = () => console.log('now');
    (
    () => new Promise(
    resolve => resolve(f())
    )
    )();
    console.log('next');
    // now
    // next
    上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
    鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。
    const f = () => console.log('now');
    Promise.try(f);
    console.log('next');
    // now
    // next
    事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
    由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
    function getUsername(userId) {
    return database.users.get({id: userId})
    .then(function(user) {
    return user.name;
    });
    }

    上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。
    database.users.get({ id: userId })
    .then(...)
    .catch(...)
    但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

     pasting

    try {
    database.users.get({ id: userId })
    .then(...)
    .catch(...)
    } catch (e) {
    // ...
    }
    上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。
    Promise.try(database.users.get({ id: userId }))
    .then(...)
    .catch(...)
    事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

    原网站:https://www.cnblogs.com/whybxy/p/7645578.html

    https://www.cnblogs.com/sunshq/p/7890504.html

    推荐阮一峰promise:http://es6.ruanyifeng.com/#docs/promise" target="_blank" >

  • 相关阅读:
    AutoCAD 2013 .net插件创建向导现在支持Map 3D,Civil 3D,AutoCAD Architecture 和AutoCAD MEP
    AutoCAD® Civil 3D API需求意愿调查
    Linux 下动态库和静态库的创建和调用
    几个典型的内存拷贝及字符串函数实现
    典型的几个链表操作-逆序和重排
    打印 N*N 螺旋矩阵
    PhoneGap开发初体验:用HTML5技术开发本地应用
    不申请变量和空间反转字符串
    寻找最大公共子字符串
    二维动态数组定义及二维静态数组与**P的区别
  • 原文地址:https://www.cnblogs.com/yunshangwuyou/p/9567239.html
Copyright © 2011-2022 走看看