我们知道js中有关异步编程的知识有很多,比如以Promise,await/async等系列的异步语法,以及XMLHttpRequest,fetch系列的js异步请求API。这篇博客我将以一个实际的应用案例来究竟如何使用异步编程来解决问题。(这里我不对这些异步语法以及异步API的使用作讲解,如果有对其不了解的读者可自行去mdn官方文档进行查阅学习。)
现在我需要使用js代码来获取网络上一跨域的图片资源,并且需要在指定的时机将其渲染到页面上。 图片资源以一个百度的图片为示例,其链接如下: https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif 效果图如下: 指定的时机是指在获取到图片资源之后再进行渲染操作。
显然此问题分为两步,第一步为获取图片,第二步为异步渲染。
首先,我们最直接的想法就是通过fetch这个api来获取图片资源,然后不难写出下面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <img src='' id='myImage'> <script> function getImage(url) { fetch(url).then(function (response) { console.log(response); if (response.ok) { return response.blob(); } throw new Error('Network response was not ok.'); }).then(function (myBlob) { var myImage = document.getElementById('myImage'); var objectURL = URL.createObjectURL(myBlob); console.log(objectURL); myImage.src = objectURL; }).catch(function (error) { console.log('There has been a problem with your fetch operation: ', error.message); }); } getImage('https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif'); </script> </body> </html>运行此代码并打开控制台,会查看到如下信息: 没错,此信息说明此操作为CORS(跨域资源共享),并且此行为被阻止了。 然后通过一系列的查询找到一种解决方案,在fetch的第二个参数中添入如下配置项:
{ mode: "no-cors" }更新后的完整代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <img src='' id='myImage'> <script> function getImage(url) { fetch(url, { mode: "no-cors" }).then(function (response) { console.log(response); if (response.ok) { return response.blob(); } throw new Error('Network response was not ok.'); }).then(function (myBlob) { var myImage = document.getElementById('myImage'); var objectURL = URL.createObjectURL(myBlob); console.log(objectURL); myImage.src = objectURL; }).catch(function (error) { console.log('There has been a problem with your fetch operation: ', error.message); }); } getImage('https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif'); </script> </body> </html>同样,运行更新后的代码并查看控制台信息: 查看网络面板:
不难发现,虽然此次请求的状态码为200,但是其返回的response却如同虚设,几乎是个空壳response。最后,再挣扎一会,终于还是放弃这条道路,因为此路本就不通。(本质原因应该是出于浏览器本身的安全策略) 退回起点,换个思路想想,既然不支持直接通过js代码获取跨域网络图片,那我们为何不直接将其URL塞给原生的ImgDOM,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <img src='https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif' id='myImage'> </body> </html>运行此代码,结果也不出意外,图片能够正确的显示在页面之中。 问题貌似解决了,但是总感觉哪里有些不太对劲。 没错,我们需要的是通过js来获取此图片,而不是直接通过HTML来实现,所以基于上面代码还需要进一步改进:(通过js代码来动态生成HTMLImageElement对象)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function getImage(url) { var myImage = new Image(600, 200); myImage.src = url; document.body.appendChild(myImage); } getImage('https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif'); </script> </body> </html>运行此代码,结果如下: 到此,经过一系列的思考加尝试,最终得到了我们想要的第一步的解决方案。
这一步将没有第一步那么麻烦,但是这一步的难点在于逻辑上的理解。 我们知道,不管是使用哪种方式,网络请求图片始终是异步操作,所以想要处理请求图片的结果,这就不得不使用异步请求的语法。(这里我使用promise) 结合异步语法的新版代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function getPromise(url) { return new Promise((resolve, reject) => { const image = new Image(600, 200); image.onload = () => { resolve(image); }; image.onerror = () => { reject(); }; image.src = url; }) } function renderImage(image) { document.body.appendChild(image); console.log('成功渲染图片'); } function errorHandler() { console.log('获取图片失败'); } function getImage(url) { getPromise(url).then( (image) => { renderImage(image); }, () => { errorHandler(); }); console.log('我先行一步'); } getImage('https://www.baidu.com/img/dong_30a61f45c8d4634ca14da8829046271f.gif'); </script> </body> </html>页面显示效果如下: 控制台的结果如下: 图片如期望进行渲染并且也是异步渲染,所以上述代码便是我们最终的预期代码。 在这里我还有一个地方需要对大家进行叙述,我之所以将最终的渲染逻辑放置到图片成功被请求之后(onload方法里),是因为存在可能有非法URL的情形,所以才不得不使用此异步操作进行封装。(有兴趣的读者可以使用一个非法的URL分别对异步代码和非异步代码进行测试,就会得到一个很amazing的结果)
本篇博客我以一个实际的应用场景为案例通过思路牵引的方式带领大家一步一步的来解决问题,相信大家也会发现,很多时候,感到困难的不是问题本身而是在于解决问题的思路。对于此篇博客内容有任何地方不理解的读者欢迎评论区留言,我看到后会给予积极回复。学习之路漫长且修远,贵在坚持,大家共同进步!