1. 单线程机制
js是一门单线程语言,即一次只能完成一个任务。
每个任务都必须按顺序执行,并且一个任务执行结束后,才可以继续执行下一个任务。
TaskA --> TaskB --> TaskC
js虽然是单线程语言,但是js运行在浏览器上,浏览器本身是多线程的,主要包含以下几个线程:
- GUI 渲染线程 ★
- JavaScript 引擎线程 ★
- 定时触发器线程 / EventLoop轮询处理线程
- 浏览器事件触发线程
- 异步http请求线程 ★
2. 单线程产生的问题
当一个任务需要大量时间开销的情况下,容易造成浏览器’假死’现象。从用户角度来看,整个程序像是已经停止运行。
3. 解决方案
为解决阻塞问题,浏览器允许异步执行一些操作,所以产生了异步操作的感念。
4. 同步JS和异步JS
同步阻塞:A调用B,B处理获得结果,才返回给A。A在这个过程中,一直等待B的处理结果,没有拿到结果之前,需要A(调用者)一直等待和确认调用结果是否返回,拿到结果,然后继续往下执行。 做一件事,没有拿到结果之前,就一直在这等着,一直等到有结果了,再去做下边的事情
一次只能在一个主线程上发生一件事情,其他所有事情都将被阻塞,直到操作完成
// 同步操作
function mainThread(){
console.log('打印A');
console.log('打印B');
let date;
for (let i = 0 ; i < 10000000 ; i++){
let dateitem = new Date();
date = dateitem
}
console.log('打印最新的日期');
console.log(date);
console.log('打印C');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
console.log('This is a newly-added paragraph');
console.log('打印D');
}
mainThread()
异步非阻塞:A调用B,无需等待B的结果,B通过状态,通知等来通知A或回调函数来处理。做一件事,不用等待事情的结果,然后就去忙别的了,有了结果,再通过状态来告诉我,或者通过回调函数来处理。
将耗时任务放在其他线程中执行,主线程在将来某个时刻收到回调函数的通知
// 回调函数处理里布操作,将耗时较长的操作作为异步操作,不影响界面的渲染
function asyncCB(callBack) {
setTimeout( function () {
let data;
for (let i = 0 ; i < 10000000 ; i++){
let dateItem = new Date();
data = dateItem;
}
callBack(data);
} , 0)
}
// 正常顺序打印数据,并打印出最新的日期
function mainThread(){
console.log('打印A');
console.log('打印B');
// 使用回调函数 - 此时不会阻塞后面函数后面代码的执行
asyncCB(function (value) {
console.log("获取最新的数据");
console.log(value);
});
// 直接打印CD
console.log('打印C');
console.log('打印D');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
console.log('This is a newly-added paragraph');
}
以下演示实例的需求背景为:获取北京的天气情况,获取成功后再次获取上海的天气情况,获取成功后再次获取其他城市的天气情况。从而达到异步场景的模拟
实现思路
函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。
演示实例
function getCityWeather(url , callBack) {
let xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return ;
if (this.status === 200){
let resData = JSON.parse(this.response);
console.log(resData);
// 使用回调函数达到异步处理的效果
if (callBack) callBack();
}
}
}
// 需求:依次输出北京、上海、天津的天气情况
getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz', function () {
getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',function () {
getCityWeather('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津')
})
});
优缺点
实现思路
将 方案1 中的回调函数事件化,触发事件执行回调函数,原理与回调函数一致。事件处理在浏览器中本身就是一种异步机制,在事件发生时再触发函数。
优缺点
实现思路
实例化一个promise对象,在promise对象中执行主函数,执行成功后resolve
结果,执行失败后reject
结果。即将回调函数的嵌套模式改为链式调用。
演示实例
单次链式调用
// 实例化一个Promise对象
let promise = new Promise(function (resolve , reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET' , 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return ;
if (xhr.status === 200) return resolve(xhr.response);
reject()
}
});
// 成功之后执行函数
promise.then(function (value) {
let xhr2 = new XMLHttpRequest();
xhr2.open('GET' , 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
xhr2.send();
xhr2.onreadystatechange = function () {
if (xhr2.readyState !== 4) return ;
if (xhr2.status === 200){
const data1 = JSON.parse(value);
const data2 = JSON.parse(this.response);
console.log(data1);
console.log(data2);
}
}
}).catch((error) =>{
console.log(error);
})
多次链式调用
// promise对象
function getJSON(url , data){
return new Promise(((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept' , 'application/json');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return ;
if (this.status === 200){
if (data){
console.log("上次执行结果");
console.log(data);
}
return resolve(this.response);
}
reject(new Error(this.statusText));
}
}))
}
// 链式调用
getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz')
.then(function (value) {
return getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',value);
})
.then(function (value) {
return getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=河北',value);
})
.then(function (value) {
return getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=山东',value);
})
.catch((error) => {
console.log(error);
})
优缺点
generator实现异步操作 - 手动执行
// 使用 Generator 封装一个异步操作
function* getCityWeather () {
let url = 'https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=北京';
let result = yield fetch(url);
document.querySelector('#cityName').innerHTML = result.city;
document.querySelector('#airTips').innerHTML = result.air_tips;
}
// 执行函数s
let gen = getCityWeather();
let response = gen.next();
response.value.then((data)=>{
return data.json();
}).then((myJson) => {
console.log(myJson);
gen.next(myJson)
})
关于thunk函数
演示案例
// thunk函数
function thunk(url) {
return function (fn) {
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept' , 'application/json');
xhr.send();
xhr.onreadystatechange = function () {
let data , error ;
if (this.readyState !== 4) return ;
if (this.status === 200){
data = this.response;
}else {
error = new Error(this.statusText);
}
fn(error,data);
}
}
}
// 声明异步调用函数
function* generator() {
let data1 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
console.log(data1);
let data2 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
console.log(data2)
let data3 = yield thunk('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津');
console.log(data3);
}
// 手动执行函数
let g = generator();
g.next().value((error , data) =>{
if (error) return g.throw(error);
g.next(data).value((error , data) => {
if (error) return g.throw(error);
g.next(data).value((error , data) => {
if (error) return g.throw(error);
g.next(data);
});
})
});
// 自动执行
function autoRun(gen) {
const g = gen();
function next(error , data) {
if (error) return g.throw(error);
const res = g.next(data);
if (res.done) return ;
res.value(next);
}
next();
}
autoRun(generator);
演示案例
// promise函数
function pm(url) {
return new Promise(((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept' , 'application/json');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return ;
if (this.status === 200){
resolve(this.response);
}else {
reject(new Error(this.statusText));
}
}
}))
}
// 声明异步函数
function* generator() {
let data1 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
let data2 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海');
let data3 = yield pm('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=天津');
console.log(data1);
console.log(data2);
console.log(data3);
}
// 手动执行函数
const g = generator();
g.next().value.then((data) => {
g.next(data).value.then((data) => {
g.next(data).value.then((data) => {
g.next(data);
})
})
}).catch((error) => {
console.log(error);
})
// 自动执行函数
function autoRun(gen) {
const g = gen();
function next(data) {
const res = g.next(data);
if (res.done) return;
res.value.then(next);
}
next();
}
autoRun(generator);
演示案例
function getJSON(url , data){
return new Promise(((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept' , 'application/json');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return ;
if (this.status === 200){
if (data){
console.log("上次执行结果");
console.log(data);
}
return resolve(this.response);
}
reject(new Error(this.statusText));
}
}))
}
// 声明 async 函数
async function getWeather() {
const data1 = await getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz');
const data2 = await getJSON('https://tianqiapi.com/api?version=v6&appid=78248274&appsecret=ey9wDoDz&city=上海',data1);
console.log(data1);
console.log(data2);
console.log("执行进行中")
return '执行结束'
}
// 执行函数
getWeather().then((resolve) => {
console.log(resolve);
});