JavaScript 之 异步请求,promise,Fetch API,闭包
一、
1、异步(async)
异步,它的孪生兄弟--同步(Synchronous),"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的.
"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。 "异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。就现在来说应该没有什么后台服务器还是同步操作了...
用最直观的代码来体现:
1 <body> 2 <button id="Button">展示异步操作</button> 3 <script> 4 var Button=document.getElementById('Button'); 5 Button.onclick=function(){ 6 alert('展示异步操作--a'); 7 } 8 alert('展示异步操作--b'); 9 </script> 10 </body>
这个简单的例子就体现出了异步和同步的区别了:
我们平常写的代码,都是从上到下来执行的,一般上面的语句还没有执行结束的情况下,下面的语句是不会执行的,但是这段代码我们很容易测试出:先弹出b窗口,当你点击按钮的时候才开始弹出a窗口。 这就是典型的异步操作,不用等把上面的语句全部执行完才开始执行下面的语句。
2、Promise
Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题。本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解。
什么是 Promise
一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。它允许你以一种同步的方式编写异步代码。例如,如果你想要使用 Promise API 异步调用一个远程的服务器,你需要创建一个代表数据将会在未来由 Web 服务返回的 Promise 对象。唯一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间,Promise 对象将扮演一个真实数据的代理角色。接下来,你可以在 Promise 对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。
Promise 对象曾经以多种形式存在于许多语言中。
去除厄运的回调金字塔(Pyramid of Doom)
Javascript 中最常见的反模式做法是回调内部再嵌套回调。
1 // 回调金字塔 2 asyncOperation(function(data){ 3 // 处理 `data` 4 anotherAsync(function(data2){ 5 // 处理 `data2` 6 yetAnotherAsync(function(){ 7 // 完成 8 }); 9 }); 10 });
引入 Promises 之后的代码
1 promiseSomething() 2 .then(function(data){ 3 // 处理 `data` 4 return anotherAsync(); 5 }) 6 .then(function(data2){ 7 // 处理 `data2` 8 return yetAnotherAsync(); 9 }) 10 .then(function(){ 11 // 完成 12 });
Promises 将嵌套的 callback,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格。Promises 不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。
3、Fetch API
Fetch API 提供了一个获取资源的接口(包括跨域)。任何使用过 XMLHttpRequest
的人都能轻松上手,但新的API提供了更强大和灵活的功能集。
Fetch 提供了对 Request
和 Response
(以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是service workers、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。
它还提供了一种定义,将 CORS 和 HTTP 原生的头信息结合起来,取代了原来那种分离的定义。
4、闭包 (closure)
闭包和词法作用域非常相近。一个关于闭包如何工作的更好或者更实际的例子就是返回一个函数的引用。
我们可以返回域中的东西,使得它们可以被其父域所用。
当你在函数里声明一个变量时,你只能在函数内访问。这些变量的作用域就被限制在函数里了。
如果你在一个函数内又定义了内部函数,那么这个内部函数就被称作闭包。它仍可以访问外部函数的作用域。
二、
其中, XMLHttpRequest(XHR)和Fetch是浏览器的原生API,jquery的ajax其实是封装了XHR。
这里我们主要比较一下Fetch与XHR在异步请求中的使用示例。
XMLHttpRequest
1 var xhr; 2 if (window.XMLHttpRequest) { // Mozilla, Safari... 3 xhr = new XMLHttpRequest(); 4 } else if (window.ActiveXObject) { // IE 5 try { 6 xhr = new ActiveXObject('Msxml2.XMLHTTP'); 7 } catch (e) { 8 try { 9 xhr = new ActiveXObject('Microsoft.XMLHTTP'); 10 } catch (e) {} 11 } 12 } 13 if (xhr) { 14 xhr.onreadystatechange = onReadyStateChange; 15 xhr.open('POST', '/api', true); 16 // 设置 Content-Type 为 application/x-www-form-urlencoded 17 // 以表单的形式传递数据 18 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 19 xhr.send('username=admin&password=root'); 20 } 21 22 // onreadystatechange 方法 23 function onReadyStateChange() { 24 // 该函数会被调用四次 25 console.log(xhr.readyState); 26 if (xhr.readyState === 4) { 27 // everything is good, the response is received 28 if (xhr.status === 200) { 29 console.log(xhr.responseText); 30 } else { 31 console.log('There was a problem with the request.'); 32 } 33 } else { 34 // still not ready 35 console.log('still not ready...'); 36 } 37 }
从上边的代码可以看出,XMLHttpRequest 是一个非常粗糙的API,不符合关注分离的原则,配置和调用方式非常混乱,前端程序员们不仅要做各个浏览器的兼容性,还饱受回调地狱的折磨,这显然不是一个好的选择。
Fetch
1 fetch(...).then(fun2) 2 .then(fun3) //各依赖有序执行 3 ..... 4 .catch(fun)
从上边的代码可以看出,fetch解决了回调地狱问题,使用简便,虽然还是有Callback的影子,但是看起来舒服多了。
Fetch 优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便,使用isomorphic-fetch
1、fetch虽然底层,但是还是缺少一些常用xhr有的方法,比如能够取消请求(abort)方法
2、fetch在服务器返回4xx、5xx时是不会抛出错误的,这里需要手动通过,通过response中的ok字段和status字段来判断