js 异步理解及总结

550 阅读6分钟

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() 微任务执行完之后才会执行宏任务 有兴趣的同学可以看看下面这道题,先遮住最后一行的答案,试着自己在纸上写下你的答案

image.png

一、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.allPromise.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.rejectPromise.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
})

  1. await 后面必须跟一个Promise对象,跟其他数据类型也可以,但是会同步执行而不是异步
  2. result 返回的是一个promise对象,通过.then()可以获取到返回的值
  3. 无需配合其他插件,可直接使用