js异步总结

本文详细介绍了JavaScript的异步机制,包括单线程、事件循环(Event Loop)、jQuery的Deferred、Promise、Generator以及async/await。重点阐述了事件循环的工作原理,如何通过任务队列处理异步任务,以及微观任务和宏观任务的区别。通过对这些概念的理解,帮助开发者更好地掌握JavaScript的异步处理。

一、单线程

    单线程:只有一个线程,只能做一件事
     原因:避免DOM渲染的冲突
     解决方案:异步

浏览器可以渲染DOM

js可以修改DOM结构
js执行的时候,浏览器DOM渲染会暂停
两段js也不能同时执行(都修改DOM就冲突了)
webworker支持多线程,但是不能访问DOM

二、event- loop

event-loop: 事件轮询,js实现异步的具体解决方案
1、同步代码,直接执行
2、异步函数先放在异步队列中
3、待同步函数执行完毕,轮询执行异步队列的函数
 

$(function () {
    $.ajax({
        url: 'xxx',
        success: function () {
            console.log(0)
        }
    }) // 访问成功后,被加入到异步队列中(看响应速度)
    setTimeout(function () {
        console.log(1)
    }, 100) // 100ms 之后被放入异步队列
    setTimeout(function () {
        console.log(2)
    }) // 立即被放入异步队列
    console.log(3) // 主进程
    // 输出结果
    // 3 2 1 0 也有可能是 3 2 0 1
})

三、jQuery的Deferred

function waitHandle() {
    var dtd = $.Deferred() //创建一个deffered对象
    var wait = function (dtd) { // 要求传入一个deferred对象
        var task = function () {
            console.log('执行完成')
            dtd.resolve() // 表示异步任务已经完成
            // dtd.reject() // 表示异步任务失败或出错
        }
        setTimeout(task, 2000)
        return dtd  // 要求返回deferred对象
    }
    // 注意,这里一定要有返回值
    return wait(dtd)
}
var w = waitHandle()
w.then(function () {
    console.log('ok 1')
}, function () {
    console.log('error 1')
}).then(function () {
    console.log('ok 2')
}, function () {
    console.log('error 2')
})
var ajax = $.ajax('./data.json')
ajax.done(function () {
    console.log('success1')
}).fail(function () {
    console.log('error')
}).done(function () {
    console.log('success2')
})

返回一个deferred对象
无法改变js异步和单线程的本质
只能从写法上杜绝callback这种形式
它是一种语法糖形式,但是解耦了代码
很好的体现:开放封闭原则(可扩展,不可修改)

// 使用jquery deferred
function waitHandle() {
    var dtd = $.Deferred() //创建一个deffered对象
    var wait = function (dtd) { // 要求传入一个deferred对象
        var task = function () {
            console.log('执行完成')
            dtd.resolve() // 表示异步任务已经完成
            // dtd.reject() // 表示异步任务失败或出错
        }
        setTimeout(task, 2000)
        return dtd.promise()  // 要求返回deferred对象
    }
    // 注意,这里一定要有返回值
    return wait(dtd)
}
var w = waitHandle() //promise对象
$.when(w).then(function () {
    console.log('ok 1')
}, function () {
    console.log('error 1')
}).then(function () {
    console.log('ok 2')
}, function () {
    console.log('error 2')
})

四、Promise

// Promise 异常捕获
 
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        // throw new Error('蒋介石的反馈')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject('图片加载失败')
        }
 
        img.src = src
 
    })
 
    return promise
}
var src = 'https://img-blog.csdnimg.cn/20181130101225329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE4MjAyMQ==,size_16,color_FFFFFF,t_702'
// 规定:then只接受一个参数,最后统一用catch捕获异常
var result = loadImg(src)
result.then(function (img) {
    console.log(img.width)
    return img
}).then(function (img) {
    console.log(img.height)
}).catch(function (error) {
    console.log(error)
})
// Promise 多个串联
 
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject('图片加载失败')
        }
 
        img.src = src
 
    })
 
    return promise
}
var src = 'https://img-blog.csdnimg.cn/20181130101225329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE4MjAyMQ==,size_16,color_FFFFFF,t_70'
var src2 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1545536978&di=81dc9fb7646de8f0033a675807276161&imgtype=jpg&er=1&src=http%3A%2F%2Fs06.lmbang.com%2FM00%2FC3%2FF5%2FecloA1wAiSWAYeE_AABfbUiKw2k506.jpg'
// 规定:then只接受一个参数,最后统一用catch捕获异常
var result1 = loadImg(src)
var result2 = loadImg(src2)
result1.then(function (img) {
    console.log('第一个图标加载完成', img.width)
    return result2
}).then(function (img) { // result2,当不返回result2时,then接收的还是result1返回的全新的Promise
    console.log('第二个图片加载完成', img.width)
}).catch(function (error) {
    console.log(error)
})
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject('图片加载失败')
        }
 
        img.src = src
 
    })
 
    return promise
}
var src = 'https://img-blog.csdnimg.cn/20181130101225329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE4MjAyMQ==,size_16,color_FFFFFF,t_70'
var src2 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1545536978&di=81dc9fb7646de8f0033a675807276161&imgtype=jpg&er=1&src=http%3A%2F%2Fs06.lmbang.com%2FM00%2FC3%2FF5%2FecloA1wAiSWAYeE_AABfbUiKw2k506.jpg'
// 规定:then只接受一个参数,最后统一用catch捕获异常
var result1 = loadImg(src)
var result2 = loadImg(src2)
// Promise.all 接收一个promise对象的数组
// 待全部完成之后,统一执行success
Promise.all([result1, result2]).then(datas => {
    // 接收到的datas是一个数组,一次包含了多个promise返回的内容
    console.log(datas[0])
    console.log(datas[1])
})
// Promise.race接收一个包含多个promise对象的数组
// 只要有一个完成,就执行success
Promise.race([result1, result2]).then(data => {
    // data即最先执行完成的promise的返回值
    console.log(data)
})
  • Promise对象的then方法会返回一个全新的Promise对象
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise,那后面then方法的回调会等等它的结束

五、generator

function * foo() {

    3、 console.log(start)

    // 使用yield关键词,向外返回一个值, 在next方法返回值,拿到这个值.
    // yield关键词并不会像return语句那样,立即结束函数的执行,只是暂停生成器函数的                                
    // 执行,直到外接下一次调用生成器的next方法
    4、 const res = yield 'foo';

    7、console.log(res) // bar
}

1、const generator = foo() // 并不会立即执行函数,而是得到一个生成器对象

2、const result = generator.next() // 调用next方法,函数开始执行,打印 start

5、 console.log(result) // {value: 'foo', done: false}

6、 generator.next('bar'); // 传入的参数会作为yield语句的返回值


function * foo() {

    console.log(start)

    // 使用yield关键词,向外返回一个值, 在next方法返回值,拿到这个值.
    // yield关键词并不会像return语句那样,立即结束函数的执行,只是暂停生成器函数的                                
    // 执行,直到外接下一次调用生成器的next方法
     try {
        const res = yield 'foo';
     } catch(e) {
        console.log(e)
     }
}

const generator = foo() // 并不会立即执行函数,而是得到一个生成器对象

const result = generator.next() // 调用next方法,函数开始执行,打印 start

console.log(result) // {value: 'foo', done: false}

generator.throw(new Error('error')) // 向生成器内部抛出一个异常

六、async/await es7体验

// then只是将callback拆分了
// async/await是最直接的同步写法
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject('图片加载失败')
        }
 
        img.src = src
 
    })
 
    return promise
}
var src = 'https://img-blog.csdnimg.cn/20181130101225329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE4MjAyMQ==,size_16,color_FFFFFF,t_70'
var src2 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1545536978&di=81dc9fb7646de8f0033a675807276161&imgtype=jpg&er=1&src=http%3A%2F%2Fs06.lmbang.com%2FM00%2FC3%2FF5%2FecloA1wAiSWAYeE_AABfbUiKw2k506.jpg'
const load = async function () {
    const result1 = await loadImg(src)
    console.log(result1)
    const result2 = await loadImg(src2)
    console.log(result2)
}
load()
        // 使用await,函数必须用async标识
        // await 后面跟的是一个Promise实例
        // 需要babel-polyfill

JS 的线程、事件循环、任务队列简介
JS 是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue)。

事件循环:JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。

任务队列:异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。

  onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。

  setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。

  ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。

主线程:JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。

大部分异步调用,都会做为宏任务,进入异步队列。而Promise、MutationObserver、ProcessnextTick都会作为微任务直接在本轮调用的末尾就执行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值