单线程的JavaScript
- 同步模式与异步模式
- 异步编程的几种方式
- Promise异步方案
- Generator异步方案
- async/await语法糖
同步模式
JavaScript在调用栈(call stack)中按顺序执行代码
console.log('global start');
function bar(){
console.log('bar task');
}
function foo(){
console.log('foo task');
bar()
}
foo()
console.log('global end');
// 输出:
// global start
// foo task
// bar task
// global end
异步模式
JavaScript在调用栈(call stack)中按顺序执行代码,异步方法会被放在消息队列(Queue)中等待执行.setTimeout属于web APIs内的异步方法,消息队列中的代码会在event loop(事件循环)刷新后开始执行
console.log('global start');
setTimeout(() => {
console.log('timer1');
}, 1800);
setTimeout(() => {
console.log('timer2');
setTimeout(() => {
console.log('inner');
}, 1000);
}, 1000);
console.log('global end');
// 输出
// global start
// global end
// timer2
// timer1
// inner
回调函数
// 回调函数测试 --封装ajax请求
function Aajx(url,callback){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status === 200 || this.status === 304){
// 成功回调
callback(this.responseText)
} else {
// 失败回调
callback(new Error('请求失败啦'))
}
}
}
xhr.open('get',url)
xhr.send()
}
Aajx('http://192.168.31.243:9099',function(res){
console.log('成功接收数据:',res)
})
回调地狱: 多个依赖于上一个回调结果的多层回调嵌套称为回调地狱,缺点: 代码不好调试,可读性差.
Promise
// Promise测试
function Ajax(url){
return new Promise((resolve,reject)=>{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status === 200 || this.status === 304){
// 成功
resolve(this.responseText)
} else {
// 失败
reject(new Error('请求失败啦'))
}
}
}
xhr.open('get',url)
xhr.send()
})
}
// 单接口
Ajax('http://192.168.31.243:9099')
.then((res)=>{
console.log('成功>',res)
})
// 多接口调用
Ajax('http://192.168.31.243:9099')
.then((res)=>{
console.log('成功>',res)
Ajax('http://192.168.31.243:9099')
.then((res2)=>{
console.log('成功2>',res2)
})
})
Generator(生成器)
第一个
next()
会先执行yield前面和里面的代码(不包括赋值),第二个next()
会执行上一个yield的赋值操作和第二个yield前面和里面的代码(不包括赋值),以此类推,最后一个next()
(此时没有yield)会执行剩余代码.如果yield后是一个异步方法,那么将会接着往下执行,不会等待异步方法执行结束后再执行.
next()的执行返回结果是: {value: “”, done: true},value是yield后的结果(可以是字符串,函数…),done表示执行完当前next是否就生成器内部就都执行结束了,true表示都结束了,false表示还有代码没有运行到.
// 例1: 生成器执行顺序
function* foo(){
console.log('start code');
var res1 = yield 'return data1'
var res2 = yield 'return data2'
console.log(res1)
console.log(res2)
console.log('end code');
}
// var gener = foo() 不会执行foo函数代码
// 调用next() 方法才会调用方法.只会执行到 yield 'return data',并且不会执行赋值操作
var gener = foo()
// 执行第一个yield
console.log(gener.next());
// 执行第二个yield
console.log(gener.next());
// 执行剩余代码
console.log(gener.next());
// 输出结果:
// start code
// {value: "return data1", done: false}
// {value: "return data2", done: false}
// undefined
// undefined
// end code
// {value: undefined, done: true}
// 例2: 第二个next传递的参数是上一个yield赋值的值
function* foo(){
console.log('start code');
var res = yield (function aa(){
return 'aa'
})()
console.log(res)
console.log('end code');
return '执行结束'
}
// var gener = foo() 不会执行foo函数代码
// 调用next() 方法才会调用方法.只会执行到 yield 后面部分,并且不会执行赋值操作
var gener = foo()
// 执行第一个yield
console.log(gener.next());
// 执行剩余代码,这个next的参数就是上述res的值
console.log(gener.next(999));
// 输出结果:
// start code
// {value: "aa", done: false}
// 999
// end code
// {value: "执行结束", done: true}
// 例3: yield后是异步函数,不会阻塞线程,会先执行外部代码
function* foo(){
console.log('start code');
var res = yield Ajax('http://192.168.31.243:9099') // value 是一个promise对象
console.log(res)
console.log('end code');
return '执行结束'
}
// var gener = foo() 不会执行foo函数代码
// 调用next() 方法才会调用方法.只会执行到 yield 后面部分,并且不会执行赋值操作
var gener = foo()
// 执行第一个yield
// 由于Ajax是个异步方法,所以会先执行后续的操作
gener.next().value.then((res)=>{
console.log(res)
})
// 执行剩余代码,这个next的参数就是上述res的值
console.log(gener.next(999));
// 输出结果:
// start code
// 999
// end code
// {value: "执行结束", done: true}
// res结果
// 例4: 实现多层嵌套请求
function* foo(){
console.log('start code');
var res1 = yield Ajax('http://192.168.31.243:9099') // value 是一个promise对象
console.log(res1)
var res2 = yield Ajax('http://192.168.31.243:9099') // value 是一个promise对象
console.log(res2)
var res3 = yield Ajax('http://192.168.31.243:9099') // value 是一个promise对象
console.log(res3)
console.log('end code');
return '执行结束'
}
// 多层请求的情况 ---使用递归实现
var gener = foo()
// gener.next()
h(gener.next())
function h(nextObj){
if(nextObj.done) return
nextObj.value.then(res=>{
h(gener.next(res))
})
}
// 输出结果:
// res1结果
// res2结果
// res3结果
// end code
async/await (Generator语法糖)
// 字节跳动面试题
setTimeout(() => {
console.log('setTimeout');
}, 0);
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
// 结果
// script start
// async1 start
// async2
// script end
// async1 end
// setTimeout
由于
async2()
是一个异步方法,所以会先执行后续的代码console.log('script end')
,然后等待结束后再执行console.log('async1 end')
,这和Generator生成器的原理有关.