张子阳的博客

首页 读书 技术 店铺 关于
张子阳的博客 首页 读书 技术 关于

ES6中的Promise和Fetch

2018-01-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浏览器下直接运行,建议想要熟悉的朋友们敲一遍代码,执行一遍以加深理解。

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