js :同步与异步 promise

1,290 阅读8分钟

进程与线程

首先我们先要来了解一下进程的概念,在js中,什么是进程呢?进程就是CPU在运行指令及加载和保存上下文所需要的时间,而线程是进程中更小的单位,描述的是执行一段指令所需要的时间。而一个进程中可以并发多个线程,每条线程并行,且执行不同的任务。今天我们就具体来聊聊单线程。

单线程

大家都熟知java是一种多线程语言,能够在同一时间执行不同的任务,往往可以做到多种任务共同执行。但是我们的JavaScript却是单线程语言,这表明,在同一个时间段内,JavaScript只会执行一个任务,也就是说,如果在同一个时间有多个任务需要执行的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,而如果前一个任务执行时间很长,后一个任务就不得不继续等待,于是又有了一个任务队列的概念。

而为什么JavaScript是一门单线程语言?引用一下阮一峰老师的话来说,JavaScript的单线程,与它的用途是有很大关系,我们都知道,JavaScript作为浏览器的脚本语言,主要用来实现与用户的交互,利用JavaScript,我们可以实现对DOM的各种各样的操作,如果JavaScript是多线程的话,一个线程在一个DOM节点中增加内容,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的。

而同样,JavaScript作为一门单线程语言,意味它可以节约内存,并且节省了上下文切换的时间,更快地实现与用户的交互。

在javas为单线程语言的基础之上,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务

    同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。就比如我们熟知的元素的渲染页面,其实就是一个同步任务。

异步任务

    异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。比如js最基础的异步setTimeout,setInterval函数,就是异步,再比如打开页面时,图片的加载,其实也是一个异步任务。

我们来看下面的代码 :

function a() {

    console.log('a');

}
function b() {

    setTimeout(() => {

        console.log('b');

    },1000)
}
function c() {

    console.log('c');

}
a()
b()
c()

看完上述代码,你们会觉得应该打印的结果为a、b、c。实际上,虽然我们执行函数的顺序为a,b,c,但是我们打印的结果顺序却为a、c、b,这是为什么呢?这是因为由于方法b中的setTimeout为异步函数,延迟一秒钟进行,所以先执行了同步函数,也就是a和c,其次才执行b。那么为了正确的打印出a、b、c这个顺序的结果,我们采用后面的promise方法。

ajax

在此之前我们先聊聊别的,一说到异步,可能很多人会想到ajax,也就是用jQuery封装后的.ajax( )

Ajax 在浏览器与 Web 服务器之间使用异步数据传输(HTTP 请求),这样就可使网页从服务器请求少量的信息,而不是整个页面。

但是在使用回调函数时,由于多个回调函数相互嵌套以及不断的调用,可能会出现“回调地狱”的问题。这个时候我们也会选择promise方式

$.ajax({

            url: 'https://autumnfish.cn/search?keywords="断桥残雪"',    //像此网址发送请求

            method: 'get',

            success: (res) => {//回调函数
                singsong()
               function a ({
               function b({
               function c()
               })
             })     //可能出现 回调地狱 这个问题 
            }
        })
        getMusic()    
        
      function singSong() {  

      console.log('sing....'); 
 
 }
 

代码分析

这里我们的ajax( )就是一个异步函数,我们向一个歌曲网站发出请求,里面存有歌单,我们拿到当中的歌单,然后定义唱歌singsong( )函数。按照正常逻辑顺序来说,应该是先拿到歌单,再进行唱歌活动,但是打印的结果却是先唱歌,后拿到歌单。这是为什么呢?我们都知道,向网络端发送请求以及获得数据都是需要时间加载的,也就是说,虽然代码在前面,但singsong( )函数为同步函数,ajax( )为异步函数,需要时间调用,那么根据JavaScript的单线程语言特性,这个结果也就容易理解了。 但是因为singsong( )函数的执行必须要依赖函数ajax( )的执行结果,于是我们就可以在ajax( )内部使用回调函数 success( ),将之后要执行的函数放置到success里面,这样执行结果就正常了。但是由于可能会调用多种函数,导致回调地狱,我们也会用promise方式。

promise

看到这里,你是不是会感觉promise非常强大,那么我们通过下面这个例子来认识一下promise

function child() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('童年');

            resolve('okok') //需要此举 then生效

        }, 2000)

    })
}
function marry() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('结婚了');

            resolve('ok') //需要此举 then生效

        }, 1000)

    })

}
function baby() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('大儿');

            resolve('okokok') //需要此举 then生效

        }, 500)

    })

}
function baby2() {

    console.log('小儿');

}

 child().then(marry) //promise才拥有

child()

    .then(() => {

        return marry()

    })

    .then(() => {

       return  baby()

    })

 .then(() => {

        baby2()

    })
/*
 child()
.then(marry)
.then(baby)
*/

在分析代码前,我们先来好好认识一下这位promise

(1)promise的三个状态

Promise有三种状态,分别是:Pending(待定态)Resolved(完成态)Rejected(拒绝态)。Promise从Pending状态开始,如果任务成功就转到成功态,并执行resolve回调函数;如果失败就转到失败状态并执行reject回调函数,一个promise的状态只可能从“待定”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换。而且在promise中,resolve必不可少,拒绝态则可有可无,这个要根据具体要求来看。但是如果没有成功态,后续将不能调用链式then。

(2)promise对象必须实现then方法,promise当中的核心就是then,也是最为重要的一个方法,而且then方法必须返回一个promise对象,同一个promise对象可以注册多个then方法,并且回调的执行顺序和他们注册的顺序一致。

(3)then方法接收两个回调函数,成功回调以及失败回调,成功回调必须要有。

我们利用promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

代码思考

我们继续回到代码,上述代码中,定义了四个函数,粗略简短地表示人的一生。分别为child()、marry()、baby()、baby2(), 其中前三个函数都为异步函数,内部定义了setTimeout函数,分别延迟了2s、1s、0.5s执行。如果我们不增加promise那么执行结果就为baby2(),baby(),marry(),child(),这真就相当于你的人生逆着走了,但是加了我们的promise,可以让你的人生顺利地走向巅峰。

我们在child()函数里面定义了promise对象,并在promise对象中传入参数resolve与reject来表示它的成功态与拒绝态。实际上这是它本来就具有的,并且在函数中要定义成功态,也就是resolve,只有定义了这个resolve才能使我们的then生效,而且还要返回我们的promise对象。这样才能用 child().then(marry)的方式,使得marry()的执行在child()之后。后续的代码也是一样的方式,最后实现正确的顺序。

我们再看一个例子:

function a() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('aaa');

            resolve('yes')

        }, 1000)

    })
}

function b() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('bbb');

            resolve('yes')

        }, 800)

    })
}

function c() {

    setTimeout(() => {

        console.log('ccc');

    }, 500)

}

// a().then(b).then(c)

Promise.all([a() , b()]).then(c) //a,b执行完后才能执行c




Promise.race([a() , b()]).then(c) // 谁先执行完,马上执行c 

代码分析

这里需要注意的是,promise的其他用法:

Promise.all([a() , b()]).then(c) ,a,b函数全部执行完后才能执行c。

Promise.race([a() , b()]).then(c) ,函数a,b当中任何一个先执行完,就马上执行c。

这些都是可能函数调用出现的顺序,而promise也能实现这样的要求。

总结

promise的使用还是比较简单的,这里我们也只是初步了解,还是需要多学习,多总结。

我是小白,如果本篇文章存在错误,请在下方评论指出,谢谢各位!