JavaScript异步编程

采用单线程模式工作的原因

最早js语言就是运行在浏览器端的语言,目的是为了实现页面上的动态交互。实现页面交互的核心就是DOM操作,这就决定了它必须使用单线程模型,否则就会出现很复杂的线程同步问题。
假设在js中有多个线程一起工作,其中一个线程修改了这个DOM元素,同时另一个线程又删除了这个元素,此时浏览器就无法明确该以哪个工作线程为准。所以为了避免线程同步的问题,从一开始,js就设计成了单线程的工作模式。所以,js执行环境中负责执行代码的线程只有一个。

单线程的优势和弊端

这种模式最大的优势就是更安全,更简单,缺点也很明确,就是如果中间有一个特别耗时的任务,其他的任务就要等待很长的时间,出现假死的情况。

为了解决这种问题,js有两种任务的执行模式:同步模式(Synchronous)异步模式(Asynchronous)

同步模式与异步模式

同步模式API和异步模式API的特点:

  • 同步模式的API的特点就是任务执行完代码才会继续往下走,例如:console.log

  • 异步模式的API的特点就是下达这个任务开启的指令之后代码就会继续执行,代码不会等待任务的结束

同步模式

同步模式 :指的是代码的任务依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序的执行顺序和代码的编写顺序是完全一致的。在单线程模式下,大多数任务都会以同步模式执行。

    console.log('hello')
    function fn () {
        console.log('fn') 
    }
    function say () {
        console.log('say hello')
        fn()
    }
    say()
    console.log('bye')

    // hello
    // say hello
    // fn
    // bye

    // 使用调用栈的逻辑

异步模式

异步模式: 不会去等待这个任务的结束才开始下一个任务,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。

    console.log('timer1')
    // 延时器
    setTimeout(function timer1 () {
        console.log('timer2')
    }, 1800)
    // 延时器中又嵌套了一个延时器
    setTimeout(function timer2 () {
        console.log('timer3')
        setTimeout(function inner () {
            console.log('timer4')
        }, 1000)
    }, 1000)
    console.log('timer5')

    // timer1
    // timer5
    // timer3
    // timer2
    // timer4

js线程某个时刻发起了一个异步调用,它紧接着继续执行其他的任务,此时异步线程会单独执行异步任务,执行过后会将回调放到消息队列中,js主线程执行完任务过后会依次执行消息队列中的任务。js是单线程的,浏览器不是单线程的,有一些API是有单独的线程去做的。

Promise——优雅的异步编程方案

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise汉译过来就是承诺的意思,比传统的解决方案——回调函数和事件——更合理和更强大。Promise简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise的特点

  • 它有三种状态:pending(进行中)、 (已成功)和rejected(已失败)

  • 它的状态是不可逆的,一旦状态改变,就不会再变。

  • 即便promise中没有任何的异步操作,then方法的回调函数仍然会进入到事件队列中排队。

Promise的基本用法

  • resolve
    let promise=new Promise((resolve,reject)=>{
        console.log('hello')
        resovle('hi')
    })

    promise.then((data)=>{
        console.log(data)
    },(erro)=>{
        console.log(erro)
    })

    //hello 
    //hi
  • reject
    let promise=new Promise((resovle,reject)=>{
        console.log('hello')
        reject('erro')
    })

    promise.then((data)=>{
        console.log(data)
    },(erro)=>{
        console.log(erro)
    })

    //hello
    //erro

Promise案例

  • 使用Promise封装一个ajax请求:
    function ajax(url){
        return new Promise((resolve,reject)=>{
            // 创建一个XMLHttpRequest对象去发送一个请求
            const xhr=new XMLHttpRequest();
            // 先设置一下xhr对象的请求方式是GET,请求的地址就是参数传递的url
            xhr.open('GET',url)
            // 设置返回的类型是json,是HTML5的新特性
            // 我们在请求之后拿到的是json对象,而不是字符串
            xhr.responseType='json';
            xhr.onload=()=>{
                console.log(xhr.status)
                if(xhr.status==200){
                    //请求成功,返回结果
                    resolve(xhr.response)
                }else{
                    //失败返回错误信息
                    reject(new Error(xhr.statusText))
                }
            };
            //开始执行
            xhr.send();
        })
    };


    ajax('app.json').then((res)=>{
        console.log(res)
    },(err)=>{
        console.log(err)
    })

Promise链式调用

  • Promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined

  • 后面的then方法就是在为上一个then返回的Promise注册回调

  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数

  • 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束