JavaScript异步编程

最近有一个活,纯用 HTML+CSS+JS 写了一个插件。边学边写,期间对JS异步机制里的Promise很困惑,学明白了之后在这里写篇文章记录下。

同步和异步

调用一个同步函数意味着,调用者函数将控制权交给被调用函数,调用者函数自然是处于阻塞状态的。直到被调用函数返回,调用者函数才可以继续运行。
调用一个异步函数意味着,调用者函数不将控制权交给被调用函数,仅相当于发布一项任务。调用函数可以继续执行,也可以等待异步函数返回后继续执行。
同步函数就不解释了,异步函数举一个例子来解释:

function syncFunction(){
    console.log(1)
    delayLog(2)
    console.log(3)
}

async function delayLog(x){
    setTimeout(() => {
        console.log(x)
    }, 1000);
}

syncFunction()

输出结果为1,3,2。在syncFunction中,执行至delayLog(2)时,效果只是相当于发布了一项任务,发布的瞬间开始执行。
这里还需要额外解释一下setTimeout,这本身也是异步函数,所以有一点循环定义的尴尬…但这个还是很容易理解的,在1000毫秒之后打印x。调用setTimeout不会阻塞调用者函数。(虽然调用者delayLog就只有这一行)

Promise

async函数的机制与Promise类是密不可分的,要理解JS中的异步机制,总是无法绕过Promise。
下面展示了一个Promise对象的构造过程

const promise = new Promise((resolve, reject) => {
    // 用同步的方式消耗一点时间
    for(var i = 0; i <= 1000000000; i++){}
    
    resolve("promise fulfilled")
})

构造一个Promise对象需要传入一个执行器函数。
执行器的功能是自定义的,在上面的例子里,功能就是空转一段时间后 resolve(“promise fulfilled”)。这里的 resolve 执行器函数的参数。
执行器函数传入的参数 resolve,reject是函数,resolve表示任务成功时返回结果的动作,reject表示任务异常时返回错误的动作。例如,如果执行器内部发起了一个http请求,成功收到回复后,就可以resolve请求的结果;如果出现错误,就可以reject错误原因。
到这里还没有说清楚”resolve请求的结果“,”reject错误的原因“是返回到了哪里,后面会解释。
2. 执行上面的代码不会得到任何输出。如果我们希望在执行器函数执行完成后紧接着做一点动作,例如查看一下resolve值的长度,那么可以这样做

const promise = new Promise((resolve, reject) => {
    // 用同步的方式消耗一点时间
    for(var i = 0; i <= 10000000000; i++){}
    
    resolve("promise fulfilled")
})
promise.then(function(res){
  console.log(res.length)
})

程序会在若干时间后打印长度为17。这里调用了promise的then方法,作用是为promise对象注册了一个回调函数,回调函数接收resolve值,并完成相应功能。
reject则是相似的,如果我们把resolve(“promise fulfilled”) 改为 reject(“promise failed”),并把promise.then改为promise.catch,效果是相同的,只不过代表的含义不同。即这表示了一个失败的promise执行。
另外,有更简洁的代码书写方式,上面那么写只是便于理解,通常会写成下面这样。

const promise = new Promise((resolve, reject) => {
    // 用同步的方式消耗一点时间
    for(var i = 0; i <= 10000000000; i++){}
    
    resolve("promise fulfilled")
}).then((res)=>{
   console.log(res.length)
}).catch((e)=>{
   console.log(e)
})
  1. promise 的状态
    现在可以很容易地理解,promise具有三种状态,pending(正在运行),fulfilled(已经resolve),rejected(已经reject)。
    当我们 new 一个Promise 对象时,这个对象会立即开始执行执行器函数,而不阻塞主程序,该对象则处于pending状态。所以,在第一段关于Promise的代码中,那样做不会有任何事情发生,因为没有为promise注册任何回调函数,promise内部也不输出。

async函数

当我们调用一个async函数时,则会将这个函数包裹成一个Promise对象返回。下面是一段代码

async function delayReturn(x){
    await new Promise((resolve)=>{
        setTimeout(resolve, 5000)
    })
    console.log("function finished")
    return x
}

console.log(delayReturn(1)))

这里面使用了await关键字,后面会解释它的作用。
在这里,delayReturn的作用就是在5秒后打印信息并返回输入值。运行上面的程序,会立即打印出一个pending状态的promise对象,5秒之后输出一行信息。
async函数在被调用后就返回一个”承诺“,通常是处于pending状态的(只要这个承诺的实现需要一定时间)。这个承诺的内容就是异步函数中的内容。
要正确的输出结果1,应该为返回的promise对象注册回调函数,也就是调用then方法

async function delayReturn(x){
    await new Promise((resolve)=>{
        setTimeout(resolve, 5000)
    })
    console.log("function finished")
    return x
}

delayReturn(1).then((res)=>{
  console.log(res)
})

await 关键字

上面的代码里已经使用了await关键字。它可以理解成单目运算符,等待一个Promise对象的完成并返回它的resolve结果。在等待期间,主程序可以让出执行线程,处于阻塞状态。
因此,上面的三行代码相当于实现了一个sleep函数:定义一个承诺,内容只包括setTimeout。需要注意的是,setTimeout的调用是立即完成的,而且不造成阻塞。关键在于,把resolve注册成倒计时结束的回调函数。由于Promise直到倒计时结束前都没有resolve,从而一直处于pending状态,直到5秒后才被解决。因此,await这个promise就需要阻塞5秒,尽管await到的值本身没有任何意义。
await关键字只能用在async函数中。通常,一些编程框架提供的网络请求,数据存储等接口都是异步函数。为了保证逻辑的正确,我们的每一步操作都必须等待promise被解决。这就需要用到await关键字,从而也使调用这些操作的函数也必须被声明为异步的。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值