zoukankan      html  css  js  c++  java
  • 一篇文章彻底搞懂es6 Promise

    前言

    Promise,用于解决回调地狱带来的问题,将异步操作以同步的操作编程表达出来,避免了层层嵌套的回调函数。

    既然是用来解决回调地狱的问题,那首先来看下什么是回调地狱

    var sayhello = function(callback){
        setTimeout(function(){
            console.log("hello");
            return callback(null);
        },1000);
    }
    sayhello(function(err){
        console.log("xiaomi");
    });
    console.log("mobile phone");
    
    输出结果 
    mobile phone
    hello
    Xiaomi
    

    看上面这段代码,假如我们需要对输出内容的顺序进行调整,例如依次打印xiaomi apple huawei ,那么我们之前的做法是怎么样的

    var sayhello = function(name, callback){
        setTimeout(function(){
            console.log("hello");
            console.log(name);
            return callback(null);
        },1000);
    }
    sayhello("xiaomi", function(err){
        sayhello("apple", function(err){
            sayhello("huawei", function(err){
                console.log("end");
            });
        });
    });
    console.log("mobile phone");
    

    问题很明显,代码层层嵌套,看起来十分的混乱,如果层级代码更多更是难以维护

    因此Promise的出现使得我们可以用同步的方式来操作异步代码,解决以上问题

    初识promise

    var getUserInfo = function() {
        return new Promise(function(resolve) {
            setTimeout(function() {
                var user = {name: 'Kerry Wu', age: 31};
                resolve(user);
            }, 3000);
        });
    };
    this.getUserInfo().then(function(userInfo) {
        console.log('userInfo',userInfo);//{ name: 'Hanmeimei', age: 31 }
    });
    

    我们通过new关键词实例化一个Promise对象并返回该对象,然后使用.then的形式获取Promise返回的内容

    这里需要注意的是,new Promise 实例化是一个同步的过程,而.then是一个异步的过程,关于同步异步执行顺序 ,先执行同步在执行异步代码

    Promise状态

    1、Pending 进行中 / Resolved 已成功 / Rejected 已失败

    resove 将未完成变成已完成 pending => resolved
    reject 将未完成变成已失败 pending => rejected

    var promise = new Promise(function(resolve, reject) {
      // ... some code
    
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    

    2、then 与 catch
    then方法接收两个函数参数,第一个表示resove 已成功的回调,第二个表示reject 已失败的回调

    用法如下:

    var p = new Promise(function(resolve, reject){ ... })
    p.then(function(){}, function(){})
    p.then().catch();
    

    then,前一个then的返回结果,可以再后一then的回调中获取,如:

    var p3 = ()=> new Promise((resolve, reject)=>{
         resolve('{"name":"jack", "age":28}')
    });
    
    p3()
       .then(res => JSON.parse(res))
           .then(data => Object.assign(data, {name:'rose'}))
       .then(data => console.log(data))
    // 输出:{name: "rose", age: 28}
    

    异步加载图片

    function loadImageAsync(url) {
      return new Promise(function(resolve, reject) {
        var image = new Image();
    
        image.onload = function() {
          resolve(image);
        };
    
        image.onerror = function() {
          reject(new Error('Could not load image at ' + url));
        };
    
        image.src = url;
      });
    }
    
    loadImagesAsync('//img.static.com/xxx.jpg').then(function(img){
        //加载成功 显示图片
    }, function(err){
       //加载失败 提示失败
    })
    

    异步加载数据
    使用promise包装一个异步请,返回一个promise对象,使用then和catch的方式对返回结果进行处理

    var getJSON = function(url){
      return new Promise((resolve, reject)=>{
                 var client = new XMLHttpRequest();
              client.open('GET', url);
              client.onreadystatechange = callback;
                   client.send();
    
              function callback(){
                      if(this.readyState !== 4) return;
    
                     if(this.status === 200){
                              resolve(this.response)
                      }else{
                              reject(new Error(this.statusText))
                         }
               }
       })
    }
    
    getJSON('/api/getList').then(function(data){
       //获取请求的数据
    }, function(err){
         //请求失败错误处理
    });
    

    catch
    p.catch()用于处理promise中rejected状态的回调,与p.then(resolveFn, rejectFn)中 rejectFn的作用相同

    var p = new Promise(function(resolve, reject){ ... });
    p.then(function(){}, function(){});
    //等同于
    p.then(function(){}).catch(function(){});
    

    reject(‘error’) 与 throw new Error(‘…’) 都能被catch捕获

    new Promise((resolve, reject) => {
            throw new Error('some error1');
    }).catch(err =>  console.log(err.message))
    
    // 等同于
    new Promise((resolve, reject) => {
       reject('some error2')
    }).catch(err => console.log(err))
    

    捕获异常
    promise对象的错误,具有 冒泡 性质,会一直向后传递,直到被捕获
    推荐使用 catch 代替then(null, rejectFn)中的rejectFn,catch可以捕获前面then函数返回的错误信息,也更接近同步的写法

    // bad
    new Promise(function(resolve, reject){}).then(resolveFn, rejectFn)
    
    // good
    new Promise(function(resolve, reject){}).then(resoveFn).catch(rejectFn)
    

    Promise all与race

    Promise.all([]) 与 Promise.race([])

    • 接收一个数组做为参数,参数中的每个元素为promise实例,
    • 如果元素不是promise实例,则会调用Promise.resolve()转换为promise的实例
    • 将多个promise对象包装为一个新的promise对象

    1、Promise.all()

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

    当p1、p2、p3的状态全部为resolved时,才能将p的状态改为resolved
    当p1、p2、p3其中一个状态变成rejected时,就会将p的状态变成rejected

    var p = Promise.all([Promise.resolve('1'), Promise.resolve('2'), Promise.resolve('3')]);
    p.then(data => console.log(data)) //["1", "2", "3"]
    
    var p1 = Promise.all([Promise.resolve('1'), Promise.reject('2'), Promise.resolve('3')]);
    p1.then(data => console.log(data)).catch(err => console.log(err)) // 2
    

    Promise.all用的最多一般是我们在请求网络数据时,比如需要同时请求多个接口,我们可以合并多个请求一次处理

    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();
    		    });
    		}
    		function getComment() {
                return getURL('http://azu.github.io/promises-book/json/comment.json').then(res=>JSON.parse(res));
            }
            function getPeople() {
                return getURL('http://azu.github.io/promises-book/json/people.json').then(res=>JSON.parse(res));
            }
            // 合并请求
    		Promise.all([getComment(), getPeople()]).then(function (value) {
    		    console.log(value);
    		}).catch(function(error){
    		    console.log(error);
    		});
    

    Promise resolve和reject

    Promise.resolve() 与 Promise.reject()

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

    最后

    记得以前在面试的时候,被问了一道很有意思的面试题,主要是考察promise和settimeout执行顺序

    setTimeout(function () {
        console.log(1)
    }, 0);
    new Promise(function executor(resolve) {
            resolve();
    }).then(function () {
        console.log(2);
    });
    

    如上代码,为什么运行结果是2,1而不是1,2?
    不是setTimeout先加入任务队列吗?

    解答:
    1、从规范上来讲,setTimeout有一个4ms的最短时间,也就是说不管你设定多少,反正最少都要间隔4ms才运行里面的回调(当然,浏览器有没有遵守这个规范是另外一回事儿)。而Promise的异步没有这个问题。
    2、从具体实现上来说,这俩的异步队列不一样,Promise所在的那个异步队列优先级要高一些。

    还有一道差不多的

    (function test() {
        setTimeout(function() {console.log(4)}, 0);
        new Promise(function executor(resolve) {
            console.log(1);
            for( var i=0 ; i<10000 ; i++ ) {
                i == 9999 && resolve();
            }
            console.log(2);
        }).then(function() {
            console.log(5);
        });
        console.log(3);
    })()
    

    为什么输出结果是 1,2,3,5,4 而非 1,2,3,4,5 ?

    解答:
    1、Promise.then 是异步执行的,而创建Promise实例( executor )是同步执行的。
    2、setTimeout 的异步和 Promise.then 的异步不太一样不在同一个队列中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.then()在本轮“事件循环”结束时执行。因此then 函数先输出,settimeout后输出。

    这里涉及到js事件循环、任务队列的东西,了解更多 https://www.cnblogs.com/hity-tt/p/6733062.html

    参考阅读

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
    https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000
    http://coderlt.coding.me/2016/07/17/ES6-promise/

  • 相关阅读:
    linux系统编程程序员必备
    postgresql查询优化之GIN(Generalized Inverted Index)索引与全文检索
    postgresql并行执行
    lightdb for postgresql日志详解
    PostgreSQL 使用数组类型
    postgresql wal文件误删恢复
    汉语词性标注集
    postgresql----JSON类型、函数及全文检索的支持
    postgresql等待事件之wait_event为空解析
    lightdb for pg查看锁
  • 原文地址:https://www.cnblogs.com/fozero/p/10423830.html
Copyright © 2011-2022 走看看