js为何会有异步
js是单线程语言,在js执行过程中,是从上到下一步一步按顺序执行的,但是在客户端会存在着大量的网络请求,如果等待着请求返回结果之后在执行下面的程序,页面就会卡顿,所以就衍生出异步操作
js异步核心原理
将callback函数作为参数传递给异步执行函数,当异步函数有结果之后自动执行callback函数
var result = $.ajax({
url: 'a.json',
success: function (res) {
console.log(res)
}
})
上面代码$.ajax()异步函数需要传入地址和callback()函数(回调函数),这个函数不会立即执行,等待异步执行函数有了结果之后才会执行
常见的异步操作有:http请求、定时器操作等
浏览器的event loop(事件轮询) 【扩展知识】
js执行代码时分为同步任务和异步任务,遇到异步任务时先不执行,会先开启一个异步任务队列,将异步任务放到队列中,待同步任务执行完成之后再执行异步任务,同时异步任务分为 宏任务 和 微任务 宏任务: setTimeOut() 、 setInterval() 微任务: Promise() 微任务执行完之后才会执行宏任务 有兴趣的同学可以看看下面这道题,先遮住最后一行的答案,试着自己在纸上写下你的答案
一、Jquery异步解决方案 $.ajax()
分为1.5版本之前和之后两个版本
1.5版本之前通过callback回调函数
$.ajax({
url:"XXX",
success:function(res){
console.log(res)
}
})
这种方式会造成函数的回调地狱问题
1.5版本之后
返回的是一个defferred对象,对象里面存在done 和 fail方法,请求完成之后根据结果调用这两个方法
var ajax = $.ajax('...url');
ajax.done(function(res){
console.log('success' , res)
}).fail(function(err){
console.log('fail' , err)
})
console.log(ajax) //defferred对象
或者
var ajax = $.ajax('...url');
ajax.then(function(res){
console.log('success' , res)
} , function(err){
console.log('error' , err)
})
then()方法第一个参数表成功 , 第二个表失败
ps:为Promise标准提供了参考
总结:defferred对象具有的属性分为两组
- defferred.resolve | defferred.reject
- defferred.then | defferred.done | defferred.fail 第一组是异步函数完成之后主动触发改变状态的函数,成功或者失败 第二组是状态变化之后才会触发的监听函数
二:Promise
promise在ES6中被标准化
基本使用
三种状态:pendding 、 fulfilled 、 rejected
状态不可逆(pendding -> fulfilled 或着 pendding -> rejected),then必须返回一个promise,多个then可以链式调用,执行顺序和他们定义时候的顺序一致
const fetch = function(params){
const promise = new Promise((resolve , reject)=>{
setTimeout(function(){
console.log('异步执行完成')
resolve(params) //执行resolve 或者 reject
},0)
})
return promise //返回原生promise对象
}
const a = fetch('a');
const b = fetch('b');
a.then((res)=>{
console.log('ok',res);
//then返回的是promise对象,可以在链式中继续.then()操作
return b
}).then(res=>{
//这里的res则获取b的值
console.log('ok',res)
}).catch(err=>{
//异常穿透,如果前面存在错误的返回直接到catch里面,err获取前面的reject传参
console.log('err' , err);
})
如果需要先获取b的值再获取a的值,则调换两个.then()函数的位置即可,在b.then()中返回a
Promise.all 和 Promise.race
Promise.all:适用场景是数组中的异步操作全部完成再进行下一步操作
.all([a , b]).then(arr=>{
console.log(arr[0])
console.log(arr[1])
//接收到的是一个arr数组,依次包含了多个promise返回的内容
})
Promise.race:适用场景是返回最先完成异步操作的promise返回值,然后进行下一步操作
.race([a , b]).then(res=>{
console.log(res)
res即最先完成的promise返回值
})
Promise.resolve
先说本质:resolve()是用来表示promise的状态为fulfilled,相当于只是定义了一个有状态的promise,promise调用then的前提是promise的状态为fufilled,只有promise调用then的时候,then里面的函数才会被推入到微任务中
Promise.resolve方法的参数分为四种情况
1、参数是一个promise实例,Promise.resolve将不做修改,原封不动的返回这个实例
2、参数是一个thenable对象
thenable = {
then:function(resolve , reject){
resolve('a')
}
}
Promise.resolve会直接将这个对象转为Promise对象,并且立即执行thenable对象的then()方法
3、参数是个字符串或者其他原始值,Promise.resolve返回一个新的promise对象,状态为resolved
4、不传任何参数,直接返回一个resolved状态的promise对象
Promise.reject和Promise.resolve方法类似,但是它返回的是promise的状态为rejected的对象
promise只是表面写法上的改变,promise内部依然是callback,没有callback就无法实现异步
三、ES6 Generator迭代器生成器
基本概念:
function* generator(){
yield 100
yield 100+100
//yield后面为函数表达式,yield类似return具有返回数据的功能,存到value中
return 300
}
var g = generator(); //g为iterator对象,可以通过.next()获取属性值
g.next() //{value:100 , done:false}
g.next() //{value:3 , done:false}
g.next() //{value:300 , done:true} //done为true表执行结束
执行generator()函数时程序会进入暂停状态,遇到yield时激活程序执行后面的表达式并返回执行结果(generator对象),然后再次进入暂停状态,直到遇到return时,预示结束不再往下执行
‘暂停’才是generator的本质,只有generator能让一段程序执行到指定位置先暂停,然后再启动再暂停,因此就可以同异步产生联系,generator + callback才能实现异步
.next()参数传递
function* G() {
const a = yield 100
console.log('a', a) // a aaa
const b = yield 200
console.log('b', b) // b bbb
const c = yield 300
console.log('c', c) // c ccc
}
const g = G()
g.next() // value: 100, done: false
g.next('aaa') // value: 200, done: false
g.next('bbb') // value: 300, done: false
g.next('ccc') // value: undefined, done: true
注意:就`g.next('aaa')`是将`'aaa'`传递给上一个已经执行完了的`yield`语句前面的变量,而不是即将执行的`yield`前面的变量
Generator + thunk函数实现异步操作
// 自动流程管理的函数
function run(generator) {
const g = generator()
function next(err, data) {
const result = g.next(data) // 返回 { value: thunk函数, done: ... }
if (result.done) {
// result.done 表示是否结束,如果结束了那就 return 作罢
return
}
result.value(next) // result.value 是一个 thunk 函数,需要一个 callback 函数作为参数,而 next 就是一个 callback 形式的函数
}
next() // 手动执行以启动第一次 next
}
// 定义 Generator
const readFileThunk = thunkify(fs.readFile)
const gen = function* () {
const r1 = yield readFileThunk('data1.json')
console.log(r1.toString())
const r2 = yield readFileThunk('data2.json')
console.log(r2.toString())
}
// 启动执行
run(gen)
四、ES7 async await Generator的语法糖
const readFilePromise = Q.denodeify(fs.readFile)
// 定义 async 函数
const readFileAsync = async function () {
const f1 = await readFilePromise('data1.json')
const f2 = await readFilePromise('data2.json')
console.log('data1.json', f1.toString())
console.log('data2.json', f2.toString())
return 'done' // 先忽略,后面会讲到
}
// 执行
const result = readFileAsync()
result.then(data => {
console.log(data) // done
})
- await 后面必须跟一个Promise对象,跟其他数据类型也可以,但是会同步执行而不是异步
- result 返回的是一个promise对象,通过.then()可以获取到返回的值
- 无需配合其他插件,可直接使用