文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Promise、Generator和Async的差异是什么

2024-04-02 19:55

关注

本篇内容主要讲解“Promise、Generator和Async的差异是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Promise、Generator和Async的差异是什么”吧!

Promise、Generator和Async的差异是什么

我们知道PromiseAsync/await函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到Promise处理异步,到Generator处理异步,再到Async/await处理异步,每一次的技术更新都使得JavaScript处理异步的方式更加优雅,从目前来看,Async/await被认为是异步处理的终极解决方案,让JS的异步处理越来越像同步任务。异步编程的最高境界,就是根本不用关心它是不是异步

异步解决方案的发展历程

1.回调函数

从早期的Javascript代码来看,在ES6诞生之前,基本上所有的异步处理都是基于回调函数函数实现的,你们可能会见过下面这种代码:

ajax('aaa', () => {
    // callback 函数体
    ajax('bbb', () => {
        // callback 函数体
        ajax('ccc', () => {
            // callback 函数体
        })
    })
})

没错,在ES6出现之前,这种代码可以说是随处可见。它虽然解决了异步执行的问题,可随之而来的是我们常听说的回调地狱问题:

所以,为了解决这个问题,社区最早提出和实现了Promise,ES6将其写进了语言标准,统一了用法。

2.Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它就是为了解决回调函数产生的问题而诞生的。

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

所以上面那种回调函数的方式我们可以改成这样:(前提是ajax已用Promise包装)

ajax('aaa').then(res=>{
  return ajax('bbb')
}).then(res=>{
  return ajax('ccc')
})

通过使用Promise来处理异步,比以往的回调函数看起来更加清晰了,解决了回调地狱的问题,Promisethen的链式调用更能让人接受,也符合我们同步的思想。

但Promise也有它的缺点:

let pro
try{
    pro = new Promise((resolve,reject) => {
        throw Error('err....')
    })
}catch(err){
    console.log('catch',err) // 不会打印
}
pro.catch(err=>{
    console.log('promise',err) // 会打印
})

之前写过一篇,讲解了Promise如何使用以及内部实现原理。对Promise还不太理解的同学可以看看~

从如何使用到如何实现一个Promise

https://juejin.cn/post/7051364317119119396

3.Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。

声明

与函数声明类似,不同的是function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* gen(x){
 const y = yield x + 6;
 return y;
}
// yield 如果用在另外一个表达式中,要放在()里面
// 像上面如果是在=右边就不用加()
function* genOne(x){
  const y = `这是第一个 yield 执行:${yield x + 1}`;
 return y;
}

执行

const g = gen(1);
//执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值
g.next() // { value: 7, done: false }
//调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行
// 执行完成会返回一个 Object,
// value 就是执行 yield 后面的值,done 表示函数是否执行完毕
g.next() // { value: undefined, done: true }
// 因为最后一行 return y 被执行完成,所以done 为 true

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。

所以上面的回调函数又可以写成这样:

function *fetch() {
    yield ajax('aaa')
    yield ajax('bbb')
    yield ajax('ccc')
}
let gen = fetch()
let res1 = gen.next() // { value: 'aaa', done: false }
let res2 = gen.next() // { value: 'bbb', done: false }
let res3 = gen.next() // { value: 'ccc', done: false }
let res4 = gen.next() // { value: undefined, done: true } done为true表示执行结束

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

怎么理解这句话?我们来看下面这个例子:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

由于yield没有返回值,所以(yield(x+1))执行后的值是undefined,所以在第二次执行a.next()是其实是执行的2*undefined,所以值是NaN,所以下面b的例子中,第二次执行b.next()时传入了12,它会当成第一次b.next()的执行返回值,所以b的例子中能够正确计算。这里不能把next执行结果中的value值与yield返回值搞混了,它两不是一个东西

yield与return的区别

相同点:

区别:

4.Async/await

Async/await其实就是上面Generator的语法糖,async函数其实就相当于funciton *的作用,而await就相当与yield的作用。而在async/await机制中,自动包含了我们上述封装出来的spawn自动执行函数。

所以上面的回调函数又可以写的更加简洁了:

async function fetch() {
  	await ajax('aaa')
    await ajax('bbb')
    await ajax('ccc')
}
// 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等待上一次请求完成后再发起请求,如果没有相互依赖的情况下,建议让它们同时发起请求,这里可以使用Promise.all()来处理

async函数对Generator函数的改进,体现在以下四点:

async函数

async函数的返回值为Promise对象,所以它可以调用then方法

async function fn() {
  return 'async'
}
fn().then(res => {
  console.log(res) // 'async'
})

await表达式

await 右侧的表达式一般为 promise 对象, 但也可以是其它的值

function fn() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1000)
        }, 1000);
    })
}
function fn1() { return 'nanjiu' }
async function fn2() {
    // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value
    // const value = await '南玖'
    const value = await fn1()
    console.log('value', value)
}
fn2() // value 'nanjiu'

异步方案比较

后三种方案都是为解决传统的回调函数而提出的,所以它们相对于回调函数的优势不言而喻。而async/await又是Generator函数的语法糖。

说了这么多,顺便看个题吧~

console.log('script start')
async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')
})
console.log('script end')

解析:

打印顺序应该是: script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout

老规矩,全局代码自上而下执行,先打印出script start,然后执行async1(),里面先遇到await async2(),执行async2,打印出async2 end,然后await后面的代码放入微任务队列,接着往下执行new Promise,打印出Promise,遇见了resolve,将第一个then方法放入微任务队列,接着往下执行打印出script end,全局代码执行完了,然后从微任务队列中取出第一个微任务执行,打印出async1 end,再取出第二个微任务执行,打印出promise1,然后这个then方法执行完了,当前Promise的状态为fulfilled,它也可以出发then的回调,所以第二个then这时候又被加进了微任务队列,然后再出微任务队列中取出这个微任务执行,打印出promise2,此时微任务队列为空,接着执行宏任务队列,打印出setTimeout

解题技巧:

到此,相信大家对“Promise、Generator和Async的差异是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯