剖析javaScript中的Promise、generator、async

码字不易,转发请注明原文链接

前言

了解js的小伙伴们知道,js是一门单线程语言。所以js要实现“异步”操作,就需要一些奇淫异巧,此三种是较为常用的js异步操作处理技巧。在本文中我会先介绍这三种的定义与用法,其次会比较三者之间的联系。在以下的例子中我会用到setTimeout函数来模拟异步。

Promise

说起Promise依稀还记得当时初学微信小程序,异步调用后台数据被他血虐的情形。在将其弄懂之后,至今一直在用Promise来处理js异步调用,直到最近才入坑async(可见Promise的强大,其实自己菜鸡 >_<)。在Promise之前,要用js进行异步调用需要用到回调函数这哥们。我们先来瞅瞅回调函数是如何处理js异步调用的:

//例1:
function sleep(second,showTime){
    setTimeout(()=>{
        showTime()
        if(second<1){
            console.log("间隔时间太短了!!!")
        }else if(second<2){
            console.log(second+"秒非常好")
        }else{
            console.log(second+"秒有点长")
        }
    },second*1000)
}

function showTime(){
    console.log("运行showTime了哦,"+ new Date())
}

sleep(2,showTime)
console.log("我不管,我先到的,"+ new Date())

在当前浏览器按F12,将以上代码复制到js终端(Console)敲回车(以后都能如此运行代码,偷懒一下,当然也可以用node.js运行),运行后发现showTime()函数晚2秒钟执行。大功告成!!!那是不是这样就足够了呢,显然不是,客户的需求永远都是奇形怪状的,如果有多层异步操作嵌套怎么办?如果继续用函数回调,则代码形式如下:

//例2:
sleep(1,()=>{
	showTime()
    sleep(2,()=>{
    	showTime()
        sleep(3,()=>{
            showTime()
        })
    })
})
console.log("我不管,我先到的,"+ new Date())

代码看着感觉非常的酸爽,可能写的时候非常爽,但是到了后期维护的时候看到这种代码想屎的心都有了。。。这也是回调函数的弊端。正是回调函数有了这种弊端,所以在es6中引入了Promise对象,我们先看下Promise是如何处理例1的。

function sleep(second,showTime){
    return new Promise(function(resolve,reject){
        setTimeout(()=>{
            showTime()
            if(second<1){
                reject("间隔时间太短了!!!")
            }else if(second<2){
                resolve(second+"秒非常好")
            }else{
                resolve(second+"秒有点长")
            }
        },second*1000)
    })
}
function showTime(){
    console.log("运行showTime了哦,"+ new Date())
}
sleep(2,showTime).then(res=>{console.log(res)}).catch(err=>{console.log(err)}) //输出“时间非常好”
console.log("我不管,我先到的,"+ new Date())
sleep(0.2,showTime).then(res=>{console.log(res)}).catch(err=>{console.log(err)}) //输出“间隔时间太短了!!!”

以上代码运行一下就能对Promise有个大概的认识。此方法是通过返回Promise对象,然后通过then与cathch方法获得异步调用中的值,需要注意的是then和catch方法返回的也是Promise对象。具体就是在sleep函数中返回了一个Promise初始化对象,初始化参数是一个回调函数,此回调函数又有两个参数,分别是resolve和reject(这两货也是回调函数)。resolve用于返回成功状态时的值,用Promise对象的then函数获取。reject用于返回失败状态的值,用Promise对象的catch函数获取。

接下来可以利用Promise来实现例2的异步调用:

function sleep(second,showTime){
    return new Promise(function(resolve,reject){
        setTimeout(()=>{
            showTime()
            if(second<1){
                reject("间隔时间太短了!!!")
            }else if(second<2){
                resolve(second+"秒非常好")
            }else{
                resolve(second+"秒有点长")
            }
        },second*1000)
    })
}
sleep(1,showTime).then(res=>{
    console.log(res);
    return sleep(2,showTime);
}).then(res=>{
    console.log(res);
    return sleep(3,showTime);
}).then(res=>{
    console.log(res);
    return sleep(0.3,showTime);
}).catch(err=>{
    console.log(err);
})

相比于回调函数看起来更加的清晰,更加的有条理,装b的说就是就是相较于回调函数,可以避免进入“回调地狱”。更具体的Promise对象描述可在MDN上学习,传送门

generator

在介绍async之前,还是想先介绍下generator,毕竟阮一峰大佬曾说过:“async 函数就是 Generator 函数的语法糖”。其实generator也是在Promise的基础上实现异步调用的。只不过并不是利用Promise中的then和catch方法来获取异步处理后的值,代码更偏向于同步的形式。generator是通过function* 来定义的,并且在函数中不断通过yield关键字来多次返回值,并通过next函数获取每次返回的值。通过例2代码实现如下:

function sleep(second,showTime){
    return new Promise(function(resolve,reject){
        setTimeout(()=>{
            showTime(second)
            if(second<1){
                reject("间隔时间太短了!!!")
            }else if(second<2){
                resolve(second+"秒非常好")
            }else{
                resolve(second+"秒有点长")
            }
        },second*1000)
    })
}

function* gen(){
    let s3 = yield sleep(9,showTime);
    let s2 = yield sleep(5,showTime);
    let s1 = yield sleep(2,showTime);
    console.log("s1"+s1);
    console.log("s2"+s2);
    console.log("s3"+s3);
}
let g = gen();
g.next(); //触发s3 
g.next(); //触发s2 
g.next(); //触发s1 
g.next(); //出发剩余操作,三个console操作。
console.log("我不管,我先到的,"+ new Date())

每运行一个next函数,都会依次触发yield 获取一个返回值。后续4个g.next()操作时间间隔非常短,远小于2秒,所以会发现三个console操作会先打出来,然后0秒过后会返回是s1,再3秒过后会返回s2,再4秒过后会返回s3.

具体有关generator的用法可参考廖雪峰大佬的教程,传送门

async

在介绍完generator后,感觉还是比较麻烦,每次调用异步程序都得去解析next(),并且如果在返回s2的过程中需要s3返回值的话,用generator是比较麻烦的。所以能不能有一个更好的办法呢?庆幸的是,es7中引入了async和await函数,完美的解决了以上顾虑,利用async解决例2问题如下:

async function gen_async(){
    let s1 = await sleep(1,showTime);
    let s2 = await sleep(2,showTime);
    let ss = await sleep(0.5,showTime);
    let s3 = await sleep(3,showTime);
    console.log("s1:"+s1);
    console.log("s2:"+s2);
    console.log("s3:"+s3);
}
try{
gen_async()
}catch{
	con
}

运行下代码会发现这已经非常接近同步的形式了。而且与自己心中想的是一样的,没有回调函数的回调地狱,也不会由then的链式调用,更不会有next的看似冗余操作。形式和同步操作一样,并且真能解决异步操作,目前为止算是最好的方法了。但是需要注意的一点就是,代码在返回ss时报错,async不会捕获异常,所以会终止程序,导致无法返回s3,所以我们在用async时需要记住用try…catch…来手动捕获异常,如下所示:

async function gen_async(){
    try{
        let s1 = await sleep(1,showTime);
        let s2 = await sleep(2,showTime);
        let ss = await sleep(0.5,showTime);
        let s3 = await sleep(3,showTime);
        console.log("s1:"+s1);
        console.log("s2:"+s2);
        console.log("s3:"+s3);
    }catch{
        console.log("有异常啦,快去处理吧")
    }
}
gen_async()

另外还需要注意的是await只能在申明了async的函数下使用,具体详情可参考阮一峰的教程,传送门

Promiseasync和await是JavaScript用于处理异步操作的关键字。它们的实现原理如下: 1. PromisePromise是一种表示异步操作的对象,它包含三种状态(pending、fulfilled、rejected)。当一个异步操作完成时,Promise可从pending状态转变为fulfilled状态,表示操作成功;或者从pending状态转变为rejected状态,表示操作失败。Promise提供了then方法来处理操作的结果,并可以进行链式调用。 2. async/await:async函数是Generator函数的一种语法糖,用来简化异步操作的处理。通过在函数前添加async关键字,函数返回值将被自动封装成Promise对象。await关键字只能在async函数使用,用于暂停async函数的执行,等待Promise对象的状态改变后再继续执行。当await后面的异步操作完成时,它会返回Promise对象的结果。 下面以一个获取用户信息的案例来分析Promiseasync和await的用法: 使用Promise实现: ``` function getUserInfo() { return new Promise((resolve, reject) => { setTimeout(() => { const user = { name: 'John', age: 25 }; resolve(user); }, 1000); }); } getUserInfo() .then(user => { console.log(user); }) .catch(error => { console.error(error); }); ``` 使用async/await实现: ``` function getUserInfo() { return new Promise((resolve, reject) => { setTimeout(() => { const user = { name: 'John', age: 25 }; resolve(user); }, 1000); }); } async function displayUserInfo() { try { const user = await getUserInfo(); console.log(user); } catch (error) { console.error(error); } } displayUserInfo(); ``` 以上两种方式都可以获取用户信息,并在操作完成后打印到控制台。使用async/await可以使代码看起来更加简洁和易读。通过在async函数内部使用await关键字,可以将异步操作的代码写成类似同步代码的形式,提高了代码的可读性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值