同步任务/异步任务,微任务/宏任务
ajax
Promise
generator
await/async
- js是单线程的,意思就是一个时间只能处理一个任务。
举个例子,超市排队结账时,一个收银员一个时间只能对一个客户结账,结完一个才能结下一个。如果有些人买的东西太多了,后面的人就会等很久。就有了同步任务和异步任务 -
同步任务和异步任务
image.png
- 异步任务会进入Event Table,当时间到了,推入任务队列中。任务队列中先入先出。
- 主线程执行完毕空闲,才会去读取任务队列,如果任务队列中有任务就推入主程序中执行
- setTimeout倒计时1000时间到了,并不会一定立马执行回调函数,如果当前主线程中有比较复杂繁琐的任务在执行耗时久,那就意味着时间队列中的任务会延迟执行。总之要看当前主线程中是否空闲,才会去读取任务队列。
- 异步任务:setTimeout,setInterval,Promise,await/sync,ajax请求,onload加载等
宏任务:setTimeout,setInterval,ajax请求,onload
微任务:Promise,await/sync
setTimeout(function(){ //异步任务
console.log(2)
},0)
setTimeout(function(){ //异步任务
console.log(4)
},1000)
console.log(3) //同步任务
//打印结果 1,3,2,4
- ajax原理
ajax:一种前端与后端的交互方式。异步更新,不重新加载整个网页的情况下,对网页某个部分数据进行局部刷新。
function doAjax(method, url, callback, data, flag) {
var xhr;
flag = flag || true;// 默认为true,如果是同步请求,页面会在请求数据时假死
method = method.toUpperCase();
// 1.创建XMLHttpRequest对象
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else { //兼容早期浏览器
xhr = ActiveXObject('Microsoft.XMLHttp');
}
// 2.监听状态变化
xhr.onreadystatechange = function () {
if (xhr.readystate === 4 && xhr.status === 200) {
// 5.读到响应数据
var res = JSON.parse(xhr.responseText);
callback && callback(res,xhr.status);
} else if (xhr.readystate === 4 && xhr.status === 404) {
callback && callback(res,xhr.status); //404失败时,把状态码也返回
}
}
// 3.建立连接,4发送请求
if (method == 'GET') {
var date = new Date(),
timer = date.getTime();
xhr.open('GET', url + '?' + data + '&timer' + timer, flag);
xhr.send()
} else if (method == 'POST') {
xhr.open('POST', url, flag);
xhr.setRequestHeader('Content-Type', 'application/json');//设置请求头
xhr.send(data);
}
}
- Promise
- 实例方法:then、catch、finally
- 通过new Promise(...)得到的是一个有状态变化的promise对象
- Promise.resolve(); //一个成功的Promise对象,状态不会改变
- Promise.reject(); //一个失败的Promise对象,状态不会改变
- Promise.all(); //监听多个promise都成功
- Promise.race(); //监听最快的结果
4.1异步任务,微任务。
console.log(1)
var p = new Promise((resolve,reject)=>{
//注意,Promise的回调函数本身是同步执行的,setTimeout的回调函数才是异步。
console.log(2);
setTimeout(function(){
console.log(3);
resolve('成功');
},0)
})
console.log(4);
p.then(function(res){
console.log(5);
});
// 打印结果:1,2,4,3,5
image.png
状态一旦成功或失败就不能被改变
var p = new Promise((resolve,reject)=>{
resolve(1); //状态改变为fulfilled
reject(2); //不会被执行
})
p.then(function(res){
console.log(res) //打印
},function(err){
console.log(err)
}).finally(()=>{
//不管成功或失败都会进入
})
// 打印结果:输出1,2没有被输出
4.2 promise的链式写法。
场景:第一个promise成功后再执行第二个promise。
注意:第一个then没有返回promise,默认就会返回一个空的promise,第二个then就是空的promise的结果。
var p = new Promise((resolve, reject) => {
console.log(1)
setTimeout(function () {
resolve('成功');
}, 0)
})
console.log(2)
p.then(function (res) {
console.log(3)
return new Promise((resolve, reject) => { //返回一个promise
setTimeout(function () {
resolve(6);
}, 500)
})
}).then(function (res) { //第一个then没有返回promise,默认就会返回一个空的promise。
console.log(res)
})
console.log(5)
//1,2,5,3,6
把上面ajax请求改为promise,多个时可使用链式写法
function ajaxPromise(url) {
return new Promise((resolve, reject) => {
doAjax(url, function (data,status) {
if (status === 200) { //请求响应成功时
resolve()
}else{ //失败时
reject()
}
})
})
}
ajaxPromise('/a.json')
.then(res => {
console.log('去请求b')
return ajaxPromise('/b.json')
},err=>{
//注意:如果a请求失败,不会去请求b了,但是还是会进入到下面then中,因为默认会返回一个空promise。
}).then(res => {
console.log('去请求c')
return ajaxPromise('/c.json')
}).then(res => {
console.log('c 成功')
})
// 所以链式操作时,要这样写
ajaxPromise('/a.json')
.then(res => {
console.log('去请求b')
return ajaxPromise('/b.json')
}).then(res => {
console.log('去请求c')
return ajaxPromise('/c.json')
}).then(res => {
console.log('c 成功')
}).catch(err=>{ //不管上面哪个promise内的请求失败,都会进入到这里,并且不会执行后面的链式语句
console.log(err)
})
4.3 Promise.reject()/Promise.resolve(),必须返回一个成功/失败的promise对象,又不需要状态变化的那种promise对象。
function foo(flag){
if(flag){
return new Promise((resolve,reject)=>{
console.log(5)
resolve('success') //异步操作
})
}else{
//当传入false时,如果返回的不是promise对象,foo调用时就不存在then方法了,会报错,所以这里需要返回一个promise对象
// return 'failed'
return Promise.reject('failed')
}
}
foo(false).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
4.4 Promise.all(),监听多个promise状态都成功,一个失败就会进失败,所有的promise成功才会进then
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log(1)
resolve(1);
},500)
})
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log(2)
resolve(2);
},1000)
})
//监听多个promise状态
Promise.all([p1,p2]).then(res=>{
//所有的promise都成功才会进来
console.log('成功',res)
}).catch(err=>{
// 只要有一个promise失败,就会进来
console.log('失败',err)
})
// 打印结果:1 2 成功 [1, 2]
4.5 Promise.race(),race有竞赛的意思。监听多个promise状态,所有结果(成功或失败)中,如果成功最快就会进then,如果失败最快就会进catch,不管是哪个promise的。
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log(1)
reject(1);
},500)
})
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log(2)
resolve(2);
},1000)
})
Promise.race([p1,p2]).then(res=>{
//如果最快的那个promise结果是成功,就会进这里
console.log('成功',res)
}).catch(err=>{
//如果最快的那个promise结果是失败,就会进这里
console.log('失败',err)
})
//打印结果:1 失败 1 2
应用场景:图片加载时间超过2s就算超时。
function getImage(){ //图片加载
return new Promise((resolve,reject)=>{
var image = new Image();
image.onload = function(){
resolve()
}
image.src = '/a.png'
})
}
function timeout(){ //超时
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject()
},2000)
})
}
Promise.race([getImage(),timeout()]).then(res=>{
console.log('成功')
}).catch(err=>{
console.log('失败')
})
- Generator
5.1 Generator基本用法,关键字 :* yield next
function* foo(){
for(let i=0;i<3;i++){
console.log('a_'+i)
yield i
console.log('b_'+i)
}
}
let f = foo(); //调用foo,函数内部语句并不会执行
console.log(f)
// 调用next,会执行到yield句末
console.log(f.next()) //a_0 {value: 0, done: false}
console.log(f.next()) //b_0 a_1 {value: 1, done: false}
console.log(f.next()) //b_1 a_2 {value: 2, done: false}
// for循环内会执行三次,执行完后,再调用next,状态已变为done: true,但会把yield后的内容执行完,
console.log(f.next()) //b_2 {value: undefined, done: true}
5.2 不能作为构造函数使用。yield只能在 Generator函数 中使用,如下会报错
function* gen(arr){
arr.forEach(item => {
yield item+1 //这里会报错,这里是在forEach的回调函数中
});
}
5.3 yield的值,就是调用next时传入的值
function* gen(x) {
let y = 2*(yield(x+1)); //yield的结果并不是5+1,而是next()传入的值
let z = yield(y/3);
return x+y+z
}
let g = gen(5)
console.log(g.next()) //{value: 6, done: false}
// next传入12,12就是上次next的yield的结果:yield(x+1)=12.于是y = 2*12;
console.log(g.next(12)) //{value: 8, done: false}
// yield(y/3) = 13; z=13; x+y+z = 5+24+13
console.log(g.next(13)) //{value: 42, done: true}
应用到ajax请求中
function request(url) {
doAjax(url, function (data, status) {
//a.json请求成功后,执行next做了两件事,1传入参数data,把data传给res1,2执行到下一个yield,也就是去请求b.json。后面的类推
getData.next(data);
})
}
function* gen() {
let res1 = yield request('static/a.json');
console.log(res1)
let res2 = yield request('static/b.json');
console.log(res2)
let res3 = yield request('static/c.json');
console.log(res3)
}
let getData = gen();
getData.next();
- await/async 书写异步 ,generator的语法糖
把上面请求改用await/async,可以看出书写很简便,像同步的写法一样。不需要像generator那样一步一步调next
async函数内,await后面的代码都是在await异步有结果后执行的,await本身就有等待,等待完成的意思。
function request(url) {
return new Promise((resolve, reject) => {
doAjax(url, function (data, status) {
resolve(data)
})
})
}
async function getData() {
let res1 = await request('static/a.json');
console.log(res1)
let res2 = await request('static/b.json');
console.log(res2)
let res3 = await request('static/c.json');
console.log(res3)
}
getData();