ES6中的Promise和Fetch

ES6中的Promise和Fetch

2018-1-24 作者: 张子阳 分类: Web前端

JavaScript是单线程执行的,因此,为了避免操作时的页面中断(体现为页面假死),可以使用回调函数。但是如果回调函数中仍然嵌套有回调函数,代码就会变得越来越不可维护。这篇文章介绍ES6如何通过Promise解决这个问题,并介绍了相关的Fetch方法。

回调地狱Callback hell

假设有一个服务接口,接受两个操作数,可以完成相加的操作,并以Json字符串返回结果。返回的结果结构如下:

{
    Data: 3,                    // 计算结果
    IsSuccess: true,            // 是否成功
    Message: "",                // 错误消息
    State:0                     // 执行的结果状态
}

本例中仅需要关注Data,其他3个值总是正确的。

那么,当要完成1+2+3+4这个操作时,如果采用传统的方式,代码可能是这样的:

function post(url, data, sucess, err){               
    var request = new XMLHttpRequest();
    
    request.onreadystatechange = function(){
        if(request.readyState == 4) {                       
            if(request.status == 200){
               sucess(request.responseText); 
            }else if(typeof err == "function"){
               err(request.status, request.responseText) 
            }
        }
    }
    
    request.open("POST", url, true);
    request.send(JSON.stringify(data))              
}


post("http://test.tracefact.net/compute/add", {x:1, y:2}, function(res){
    res = JSON.parse(res);
    var z = res.Data;      // 3             
    
    post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
        
        res = JSON.parse(res);
        var z = res.Data;  // 6
        
        
        post("http://test.tracefact.net/compute/add", {x:z, y:4}, function(res){
            res = JSON.parse(res);
            var z = res.Data;      // 10                        
            alert(z);
        });
    });         
});

http://test.tracefact.net/compute/add接口是真实存在并且可以被调用的。

可以看到,success回调函数调用了3次,嵌套了2层。简单起见,没有传入err回调函数。可以通过将url修改为错误的,或者将x传入字符串来制造错误,这里就不演示了。

使用Promise

ES6引入了Promise来解决这个问题,简单来说,Promise将一层套一层的的回调,改成链式操作。

Promise共有三种状态:pending(初始状态,既不是成功,也不是失败状态)、fulfilled( 意味着操作成功完成)、rejected(意味着操作失败)。

Promimse的构造函数接受一个函数,这个函数的两个参数分别称作resolve方法和reject方法。当任务成功时,调用resolve()方法,失败时,调用reject()方法。调用resolve和reject时,传入的值,将作为输入参数,传递到then方法的resolve和reject中。

在Promise对象上可以调用then()方法,它也接受两个方法,一个是resolve,一个是reject。then()方法返回的还是一个Promise对象,因此支持链式调用。值得注意的是:then方法中上一个resolve方法的返回值,将成为下一个then方法中resolve的输入参数。由此构成了数据的流动。

上面两段话如果不结合代码,很难理解清楚,我们继续看1+2+3+4这个例子,为了简单起见,先不使用post方法异步操作。

var p = new Promise(function(resolve, reject){
    var x = 1, y =2;                
    resolve(x+y);       // 传入结果3
});

以上代码是立即执行的,仅仅完成了1+2。那么如何利用这个计算结果完成下一步+3的操作呢?可以通过then方法。

var p = new Promise(function(resolve, reject){
    var x = 1, y =2;                
    resolve(x+y);
});

p.then(function(z){   // 这里z是上一步的计算结果3             
    var y = 3;    
    return z+y;       // 6
})

类似地,完成+4操作,只需要再then一次就可以了。

p.then(function(z){             
    var y = 3;
    return z+y;   // 6
}).then(function(z){
    var y = 4;
    return z+y;   // 10
})

接下来,我们改下post的例子,将它变成Promise版本的。

var p = new Promise(function(resolve, reject){
    var x = 1, y =2;                
    post("http://test.tracefact.net/compute/add", {x:1, y:2}, function(res){
        res = JSON.parse(res);
        console.log("step1:", res);
        resolve(res.Data);      // 3
    })
});

p.then(function(z){
    return new Promise(function(resolve, reject){
        post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
            res = JSON.parse(res);
            console.log("step2:", res);
            resolve(res.Data);      // 6
        });        
    });    
    
}).then(function(z){
    return new Promise(function(resolve, reject){
        post("http://test.tracefact.net/compute/add", {x:z, y:4}, function(res){
            res = JSON.parse(res);
            console.log("step3:", res);
            resolve(res.Data);      // 10
        });        
    });
})

reject和异常处理

resolve用来处理正常流程,reject则用来处理失败的情况,用法和resolve是类似的,例如下面,我们将x的参数改为“s”,服务端将会返回400 bad request,此时可以添加then的第二个参数reject进行处理:

var p = new Promise(function(resolve, reject){
    var x = 1, y =2;                
    post("http://test.tracefact.net/compute/add", {x:"s", y:2}, function(res){
        res = JSON.parse(res);
        console.log("step1:", res);
        resolve(res.Data);      // 3
    }, function(state, msg){
        reject(state + " " + msg)
    })
});

p.then(function(z){
    return new Promise(function(resolve, reject){
        post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
            res = JSON.parse(res);
            console.log("step2: ", res);
            resolve(res.Data);      // 6
        });        
    });    
    
}, function(msg){
    alert("step2: " + msg)
});

除了使用reject,还有一种方式,就是使用catch方法,示例如下:

p.then(function(z){
    return new Promise(function(resolve, reject){
        post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
            res = JSON.parse(res);
            console.log("step2: ", res);
            resolve(res.Data);      // 6
        });
    });    
}).catch(function(msg){
    alert("step2: " + msg)
});

一般来说,使用catch会更简单清晰一些。

fetch方法

在过去,因为缺乏统一的标准,发起ajax异步请求,在不同的浏览器下有不同的方式,主要是使用XMLHttpRequest对象和ActiveXObject("Msxml2.XMLHTTP")。在ES6中,提供了fetch方法简化了这一操作。除此以外,fetch方法返回的是一个Promise对象,因此,可以链式发起异步请求。而服务端的返回值则通过response对象传递。

再次改写上面的例子:

fetch("http://test.tracefact.net/compute/add", {
    method:"POST",
    headers:{ "Content-Type": "application/json" },
    body: JSON.stringify({x:1, y:2})
}).then(function(res){   
    return res.json();
}).then(function(res){
    return fetch("http://test.tracefact.net/compute/add", {
        method:"POST",
        headers:{ "Content-Type": "application/json" },
        body: JSON.stringify({x:res.Data, y:3})
    })    
}).then(function(res){
    return res.json();
}).then(function(res){
    console.log(res.Data);    
}).catch(function(err){
    alert("err: " + err);
});

使用fetch时第一步then返回的response对象(res),和直接使用前面post方法返回的res并不是同一个对象。这个对象的详细内容可以参考这里:https://developer.mozilla.org/zh-CN/docs/Web/API/Response。在这个response上调用json()方法,返回的也是一个Promise,然后再下一步then才能够获得服务器返回的原始对象。

总结

这篇文章主要讲述了ES6中的Promise对象和Fetch方法,上面的代码,无需Babel就可以在新版本Chrome浏览器下直接运行,建议想要熟悉的朋友们敲一遍代码,执行一遍以加深理解。

感谢阅读,希望这篇文章能给你带来帮助!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

java基础Haep(堆)和Stack(栈)区别

简单的可以理解为: heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。  stack:是自动分配变量,以及函数调用的时候所使用的一些空...

2107
来自专栏ml

jsp基础知识(基本的语法及原理)

     jsp 语法分为三种不同的类型: (1)  编译器指令: 类如: <%@ page import="java.io.*"%> (2)  脚本语法:  ...

2794
来自专栏Hongten

try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,还是在return之后执行?

上图是程序执行到了try模块中的return语句时,按下F5键,进入到finally模块,执行finally模块中的语句后,变量"i"的值发生了变化。

1073
来自专栏java思维导图

Java中高级面试题部分答案解析(2)

这里是一些题型解析,还是这些话:不一定全部正确,有一些是没有固定答案的,如果发现有错的或者更适合的答案欢迎留言矫正。

841
来自专栏北京马哥教育

一篇文章学会shell工具篇之sed

? sed工具执行原理; 有关sed的参数及action的常见操作方法; 定址; 模式空间和保持空间; 使用标签 1.首先先来了解一下什么是sed? sed叫...

3147
来自专栏SpringBoot

一个密码复杂度的验证js

在项目开发中,要求密码进行复杂度限制,现帖出来跟大家做个分享。 密码复杂要求:1、长度大于8          2、密码必须是字母大写,字母小写,数字,特殊...

3763
来自专栏IMWeb前端团队

Nodejs进阶:服务端字符编解码&乱码处理

本文作者:IMWeb 陈映平 原文出处:IMWeb社区 未经同意,禁止转载 写在前面 在web服务端开发中,字符的编解码几乎每天都要打交道。编解码一旦...

35210
来自专栏Java技术分享圈

杨老师课堂之JavaSe 部分面试题

​ JVM 是 JavaVirtual Machine 的缩写,全称是 Java 虚拟机。Java 语言的一个非常重要的 特性就是跨平台性,而 Java 虚...

893
来自专栏IMWeb前端团队

Promise接口实现之jQuery 的deferred对象

本文作者:IMWeb json 原文出处:IMWeb社区 未经同意,禁止转载 Promise是什么? 我们知道JavaScript是单线程,如果遇到某...

18410
来自专栏机器学习从入门到成神

JavaScript之作用域与变量

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

952

扫码关注云+社区

领取腾讯云代金券