美文网首页前端修仙之路
ES6-Promise从入门到实战(AJax和fetch)

ES6-Promise从入门到实战(AJax和fetch)

作者: 月上秦少 | 来源:发表于2019-07-12 16:01 被阅读2次

    Promise定义

    MDN Promise

    A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

    Promise是一个对象,用作占位符,用于延迟(可能是异步)计算的最终结果。

    一个 Promise 就是一个对象,它代表了一个异步操作的最终完成或者失败。

    本质上,Promise 是一个绑定了回调的对象,而不是将回调传进函数内部。

    Promise State

    Promise只会处在以下状态之一:

    1. Pending(待处理): promise初始化的状态,正在运行,既没有完成也没有失败的状态,此状态可以提升为fulfilledrejected状态

    2. Fulfilled(已完成): 如果回调函数实现Promise的resolve(回调函数),那么state变为fulfilled

    3. Rejected(已拒绝): 如果Promise调用过程中遭到拒绝或发生异常,state就会处于rejected状态。

    4. Settled(不变的): Promise从pending状态提升后,状态要么变为fulfilled,要么变为rejected,没有其他情况。

    即:

    Promise 状态转移:
    1. pending ---> fulfilled ---> settled
    2. pending ---> rejected ---> settled
    
    Promise 状态转移

    情景模式

    一次苏洵在家宴客,限以冷、香二字为联,并先出一联为“水向石边流出冷,风从花里过来香。”苏轼当场吟出一联:“拂石坐来衣带冷,踏花归去马蹄香。”
    

    对对子的套路很简单,就是一人出上联,一人对下联。

    function up() {
        console.log('水向石边流出冷,风从花里过来香。');
        return '水向石边流出冷,风从花里过来香。';
    }
    
    function down() {
        console.log('拂石坐来衣带冷,踏花归去马蹄香。');
        return '拂石坐来衣带冷,踏花归去马蹄香。';
    }
    
    function match () {
        // 给1s钟出上联
        setTimeout(up, 1000);
        // 给2s钟对下联
        setTimeout(down, 1000);
    }
    match(); // 水向石边流出冷,风从花里过来香。
             //拂石坐来衣带冷,踏花归去马蹄香。
    

    上面的例子很简单,down函数总是需要在up函数之后执行。

    如果我们用Promise来实现如下。

    const match = new Promise((resolve, reject) => {
        // 苏洵说
        console.log('苏洵:\n游戏规则:限以冷、香二字为联');
         const time = 1000;
        setTimeout(() => {
            console.log(`苏洵:\n我出上联,且容我思考${time / 1000}s钟!`);
            resolve(time);
        }, time);
    }).then(value => {
        console.log(`苏洵:\n经过${value / 1000}s钟思考,我的上联是:`);
        console.log('水向石边流出冷,风从花里过来香。');
        return value * 2;
    }).then((value) => {
        console.log('宾客:\n这上联出的有水平呀!');
        console.log(`宾客:且容我思考${value / 1000}s钟!`);
        return value / 4;
    }).then(value => {
            console.log(`苏轼:经过${value / 1000}s钟思考,我的下联是:`);
            console.log('拂石坐来衣带冷,踏花归去马蹄香。');
            console.log(`宾客:。。。`);
    }).then(() => {
        console.log('犬子对的妙啊,不愧我儿!nice!')
    });
    

    上述例子输出结果:

    苏洵:
    游戏规则:限以冷、香二字为联
    undefined
    苏洵:
    我出上联,且容我思考1s钟!
    苏洵:
    经过1s钟思考,我的上联是:
    水向石边流出冷,风从花里过来香。
    宾客:
    这上联出的有水平呀!
    宾客:且容我思考2s钟!
    苏轼:经过0.5s钟思考,我的下联是:
    拂石坐来衣带冷,踏花归去马蹄香。
    宾客:。。。
    犬子对的妙啊,不愧我儿!nice!
    

    不懂,没关系!啥,你说你不懂对对子,没关系, 下面我们来具体讲讲Promise的API。

    1.Promise实现——(resolve, reject) 方法

    如:foo()和bar()函数都实现promise

    function foo() {return new Promise((resolve, reject) => {
        resolve();
    })}
    
    function bar() {return new Promise((resolve, reject) => {
        resolve();
    })}
    
    // 使用
    foo() 
    .then( res => bar() ) // bar() returns a Promise 
    .then( res => { 
     console.log('Both done') 
    })
    

    2.Promise.then(onFulfilled, onRejected) 方法

    Promise的then()方法允许我们在任务完成后或拒绝失败后执行任务,该任务可以是基于另外一个事件或基于回调的异步操作。

    Promise的then()方法接收两个参数,即onFulfilledonRejected 的回调,如果Promise对象完成,则执行onFulfilled回调,如果执行异常或失败执行onRejected回调。

    简单的来说,onFulfilled回调接收一个参数,及所谓的未来的值,同样 onRejected 也接收一个参数,显示拒绝的原因。
    (ajaxCallPromise后面有实现这个方法)

    ajaxCallPromise('http://example.com/page1').then( 
     successData => { console.log('Request was successful') }, 
     failData => { console.log('Request failed' + failData) } 
    )
    

    如果请求过程失败,第二个函数将会执行输出而不是第一个函数输出。

    3.Promise.catch(onRejected)方法

    除了上述then()方法可以处理错误和异常,使用Promise的catch()方法也能实现同样的功能,这个方法其实并没有什么特别,只是更容易理解而已。

    catch()方法只接收一个参数,及onRejected回调。catch()方法的onRejected回调的调用方式与then()方法的onRejected回调相同。

    ajaxCallPromise('http://example.com/page1')
    .then(successData => console.log('Request was successful'))
    .catch(failData => console.log('Request failed' + failData));
    

    4.Promise.resolve(value)方法

    Promise的resolve()方法接收成功返回值并返回一个Promise对象,用于未来值的传递,将值传递给.then(onFulfilled, onRejected)onFulfilled回调中。resolve()方法可以用于将未来值转化成Promise对象,下面的一段代码演示了如何使用Promise.resolve()方法:

    const p1 = Promise.resolve(4); 
    p1.then(function(value){ 
     console.log(value); 
    }); //passed a promise object 
    Promise.resolve(p1).then(function(value){ 
     console.log(value); 
    }); 
    Promise.resolve({name: "Eden"}) 
    .then(function(value){ 
     console.log(value.name); 
    });
    
    // 输出
    //  4 
    //  4 
    //  Eden
    

    5.Promise.reject(value)方法

    Promise.reject(value)方法与上述Promise.resolve(value)类似,唯一不同的是将值传递给.then(onFulfilled, onRejected)onRejected回调中,同时Promise.reject(value)主要用来进行调试,而不是将值转换成Promise对象。

    const p1 = Promise.reject(4); 
    p1.then(null, function(value){ 
    console.log(value); 
    }); 
    Promise.reject({name: "Eden"}) 
    .then(null, function(value){ 
     console.log(value.name); 
    });
    // 4 
    // Eden
    

    6.Promise.all(iterable) 方法

    Promise.all(iterable) 方法可以传入一个可迭代Promise对象(比如数组)并返回一个Promise对象,当所有的Promise迭代对象成功返回后,整个Promise才能返回成功状态的值。

    const p1 = new Promise(function(resolve, reject){ 
         setTimeout(function(){ 
         resolve(); 
         }, 1000); 
    }); 
    
    const p2 = new Promise(function(resolve, reject){ 
        setTimeout(function(){ 
         resolve(); 
         }, 2000); 
    }); 
    
    // 使用
    const arr = [p1, p2]; 
    Promise.all(arr).then(function(){ 
        console.log("Done"); //"Done" is logged after 2 seconds 
    });
    

    特别需要注意的一点,只要迭代数组中,只要任意一个进入失败状态,那么该方法返回的对象也会进入失败状态,并将那个进入失败状态的错误信息作为自己的错误信息。

    const p1 = new Promise(function(resolve, reject){ 
         setTimeout(function(){ 
         reject("Error"); // 返回错误信息
         }, 1000); 
    }); 
    
    const p2 = new Promise(function(resolve, reject){ 
         setTimeout(function(){ 
         resolve(); 
         }, 2000); 
    }); 
    
    const arr = [p1, p2]; 
    Promise.all(arr).then(null, function(reason){ 
        console.log(reason); //"Error" is logged after 1 second 
    });
    

    7.Promise.race(iterable) 方法

    Promise.all(iterable) 不同的是,Promise.race(iterable) 虽然也接收包含若干个Promise对象的可迭代对象,不同的是这个方法会监听所有的Promise对象,并等待其中的第一个进入完成或失败状态的Promise对象,一旦有Promise对象满足,整个Promise对象将返回这个Promise对象的成功状态或失败状态,下面的示例展示了返回第一个成功状态的值:

    var p1 = new Promise(function(resolve, reject){ 
        setTimeout(function(){ 
        resolve("Fulfillment Value 1"); 
        }, 1000); 
    }); 
    
    var p2 = new Promise(function(resolve, reject){ 
        setTimeout(function(){ 
        resolve("fulfillment Value 2"); 
        }, 2000); 
    }); 
    
    var arr = [p1, p2]; 
    Promise.race(arr).then(function(value){ 
        console.log(value); //Output "Fulfillment value 1" 
        }, function(reason){ 
        console.log(reason);
    });
    

    接下来我们来使用Promise。

    Ajax使用Promise

    先实现一个原生的Ajax:

    const Ajax = {
      get: function(url, fn) {
        // XMLHttpRequest对象用于在后台与服务器交换数据   
        const xhr = new XMLHttpRequest();            
        xhr.open('GET', url, true);
        xhr.onreadystatechange = function() {
          // readyState == 4说明请求已完成
          if (xhr.readyState === 4 && xhr.status === 200 || xhr.status === 304) { 
            // 从服务器获得数据 
            fn.call(this, xhr.responseText);  
          }
        };
        xhr.send();
      },
      // datat应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式
      post: function (url, data, fn) {
        const xhr = new XMLHttpRequest();
        xhr.open("POST", url, true);
        // 添加http头,发送信息至服务器时内容编码类型
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
            fn.call(this, xhr.responseText);
          }
        };
        xhr.send(data);
      }
    }
    

    下面使用Ajax请求数据:

    
    const ajaxCallPromise = url => {
        return new Promise((resolve, reject) => {
            // DO YOUR ASYNC STUFF HERE 
            Ajax.get(url, function(data) {
                if(data.resCode === 200) {
                    resolve(data.message)
                } else {
                    reject(data.error)
                } 
            })
        }) 
    };
    
    // 使用:
    ajaxCallPromise('http://example.com/page1') 
    .then( response1 => ajaxCallPromise('http://example.com/page2'+response1) ) 
    .then( response2 => ajaxCallPromise('http://example.com/page3'+response2) ) 
    .then( response3 => console.log(response3) )
    
    1. 首先定义ajaxCallPromise返回类型为Promise,这意味我们会实现一个Promise对象。
    2. Promise接受两个函数参数,resolve(成功实现承诺)和reject(异常导致失败)
    3. resolve和reject这两个特有的方法,会获取对应成功或失败的值
    4. 如果接口请求一切正常,我们将会接收到resolve函数返回的值
    5. 如果接口请求失败,我们将会接收到reject返回的的值

    fetch默认使用Promise

    fetch返回一个包含响应结果的promise(一个 Response 对象)对象。

    fetch('http://example.com/movies.json')
      // 现将请求转为json
      .then(function(response) {
        return response.json();
      })
      // 在控制台打印出json
      .then(function(myJson) {
        console.log(myJson);
      });
    
    // 上传图片,处理错误
    fetch('flowers.jpg').then(function(response) {
      if(response.ok) {
        return response.blob();
      }
      throw new Error('Network response was not ok.');
    }).then(function(myBlob) { 
      var objectURL = URL.createObjectURL(myBlob); 
      myImage.src = objectURL; 
    }).catch(function(error) {
      console.log('There has been a problem with your fetch operation: ', error.message);
    });
    

    MDN fetch

    fetch优势:

    • 1.语法简洁,更加语义化
    • 2.基于标准 Promise 实现,支持 async/await
    • 3.同构方便,使用 isomorphic-fetch

    fetch的确很好用,但是有的浏览器确是undefined(比如万恶的IE),这时就需要我们手动实现ajax作为替代,或者引入polyfill。

    fetch原生支持率不高,但是引入下面这些 polyfill 后可以完美支持 IE8+:

    • 1.由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
    • 2.引入 Promise 的 polyfill: es6-promise
    • 3.引入 fetch 探测库:fetch-detector
    • 4.引入 fetch 的 polyfill: fetch-ie8
    • 5.可选:如果你还使用了 jsonp,引入fetch-jsonp
    • 6.可选:开启 Babel 的 runtime 模式,现在就使用 async/await

    参考:
    Promise使用指南
    MDN Promise

    相关文章

      网友评论

        本文标题:ES6-Promise从入门到实战(AJax和fetch)

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