今天来整理下之前在公司分享过的关于Javascript 同步/异步请求、响应的4个发展阶段;
如上图所示,主要分为4个阶段:
阶段一:同步时代->同步请求响应阶段,用户和网页交互性差、体验不好,页面执行一个耗时的网络操作就会卡住,用户必须等待操作结束才能继续和网页进行交互。如下代码所示:
这段代码的执行效果如下:
界面上用户可以交互的内容包括了输入框和按钮,Generate primes这个按钮会触发一个耗时的计算操作,一旦点击后,就会进入计算,而这时候会发现界面的其他按钮和输入框无法进行交互了,页面瞬间卡住了,用户体验差。
阶段二:AJAX时代 -> 异步请求响应阶段,可以做到局部刷新,用户不必等待请求返回,可以继续与页面上的其他组件进行交互。如下代码所示:
这段代码的执行效果如下:
点击Click to start request按钮后,就会发出请求,而在发出请求后,用户可以继续在右边的输入框内输入操作,不必等待请求执行完成,体验良好;
在等待一段时间后,请求返回响应如下:
这种异步的效果会让用户感觉界面比较流畅,没有卡顿;
阶段三:Promise时代 -> 更加优雅的异步编程方式;
上面阶段二中用AJAX技术解决了用户与网页交互卡顿、不流畅等体验差的问题,但是后来使用的过程中发现,因为AJAX技术的回调机制会产生callback hell问题(回调函数的套壳),进而导致代码可读性低,不易维护等诸多问题,如下有一段callback hell的代码:
function xmlOpe2(){
const xhr = new XMLHttpRequest();
xhr.addEventListener('loadend', () => {
log.textContent = `${log.textContent}Finished with status: ${xhr.status}`;
});
xhr.open('GET', 'https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json');
xhr.send();
}
function xmlOpe1(){
const xhr = new XMLHttpRequest();
xhr.addEventListener('loadend', () => {
xmlOpe2();
log.textContent = `${log.textContent}Finished with status: ${xhr.status}`;
});
xhr.open('GET', 'https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json');
xhr.send();
}
//AJAX: Asynchronized JAVAScript and XML
function xmlOpe(){
const xhr = new XMLHttpRequest();
xhr.addEventListener('loadend', () => {
xmlOpe1();
log.textContent = `${log.textContent}Finished with status: ${xhr.status}`;
});
xhr.open('GET', 'https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json');
xhr.send();
}
document.querySelector('#xhr').addEventListener('click', () => {
log.textContent = '';
xmlOpe();
log.textContent = `${log.textContent}Started XHR request
`;
});
document.querySelector('#reload').addEventListener('click', () => {
log.textContent = '';
document.location.reload();
});这段代码,就是在一段套壳代码的实现,xmlOpe->xmlOpe1->xmlOpe2... ,代码看上去比较复杂,如果一旦网页中出现较多的请求的异步加载操作,代码的结构将会极其复杂;所以后来有了Promise这种相对优雅的改进方式:
单个请求:
const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
console.log(fetchPromise);
fetchPromise.then( response => {
console.log(`Received response: ${response.status}`);
});
console.log("Started request...");请求的链式调用:
const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
console.log(fetchPromise);
fetchPromise
.then( response => {
// throw new Error(`HTTP error: ${response.status}`);
console.log(`Received response: ${response.status}`);
return response.json();
})
.then( json => {
// throw new Error(`json error parsed`);
console.log(json[0].name);
})
.catch( error => {
console.error(`Could not get products: ${error}`);
});
console.log("Started request...");多个请求合并:
const fetchPromise1 = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
const fetchPromise2 = fetch('invalide-schema://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
const fetchPromise3 = fetch('invalide-schema://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json');
Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
.then( responses => {
for (const response of responses) {
console.log(`${response.url}: ${response.status}`);
}
})
.catch( error => {
console.error(`Failed to fetch: ${error}`)
});会发现相比之前AJAX的代码,看起来要舒服很多,易于理解。
阶段四:Async/Await时代 -> 更加人性化的、符合直觉的异步编程方式,化异步为同步写法;
经过阶段三的演化,会发现Javascript中的异步操作其实已经变得优雅、可维护,但是这不是尽头,出现了一种更加符合直觉的写法,为什么说符合直觉呢?程序员编程往往大多数都会习惯代码一行行执行,而异步执行的代码往往是违反这种直觉的,就好比下面这份代码:
1: const fetchPromise1 = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
2: func1(fetchPromise1);
3: func2();
4: console.log("end");这份代码,第一感觉看起来,就是一句句执行,可是结果不是如此,在执行完第1句fetch操作后,fetch的操作是一个网络请求,这个网络请求其实并没有完成,如果这时候当你把参数fetchPromise1传入第2句func1中时,这是好的fetchPromise1是一个半完成状态的返回值,而不是最终结果,所以这就是为什么fetch操作一般都会配上then方法去处理请求结果,如下所示:
fetchPromise
.then( response => {
console.log(`Received response: ${response.status}`);
return response.json();
})这里的then方法其实就是对请求完成后一个完整结果的处理,那么为了避免这种异步编程方式给程序员带来的困扰以及debug时候的疑惑等问题,就产生了下面的编程方式:
async function fetchProducts() {
try {
// after this line, our function will wait for the `fetch()` call to be settled
// the `fetch()` call will either return a Response or throw an error
const response = await fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
// after this line, our function will wait for the `response.json()` call to be settled
// the `response.json()` call will either return the JSON object or throw an error
const json = await response.json();
console.log(json[0].name);
}
catch(error) {
console.error(`Could not get products: ${error}`);
}
}
fetchProducts();
console.log("request start");async/await这种编程模式就是化异步编程方式为同步编程方式,请求还是异步执行,而只是利于开发者理解,给开发者一种仿佛在写同步代码的感觉,实质上JS还是异步执行,对于程序员开发和实际程序运行可谓双赢。
async/await这对关键字是成对出现的,这也是Javascript对底层做了处理,以语法糖技术包装了一层,开发者才能有此收益。
| 留言与评论(共有 0 条评论) “” |