Home >Web Front-end >JS Tutorial >A brief analysis of the differences between Promise, Generator and Async

A brief analysis of the differences between Promise, Generator and Async

青灯夜游
青灯夜游forward
2022-02-09 10:48:351884browse

Promise and Async/await functions are both used to solve asynchronous problems in JavaScript, so what is the difference between them? The following article will introduce to you the differences between Promise, Generator and Async. I hope it will be helpful to you!

A brief analysis of the differences between Promise, Generator and Async

We know that the Promise and Async/await functions are used to solve asynchronous problems in JavaScript. From the very beginning The callback function handles asynchrony, Promise handles asynchronous, Generator handles asynchronous, and then Async/await handles asynchronous, every technical update makes JavaScript The way to handle asynchronous processing is more elegant. From the current point of view, Async/await is considered the ultimate solution for asynchronous processing, making JS's asynchronous processing more and more like synchronous tasks. The highest state of asynchronous programming is that you don’t have to worry about whether it is asynchronous.

The development history of asynchronous solutions

1. Callback function

From the early Javascript code, in ES6 Before its birth, basically all asynchronous processing was implemented based on callback functions. You may have seen the following code:

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

Yes, before the emergence of ES6, this kind of code can be said to be everywhere . Although it solves the problem of asynchronous execution, it is followed by the Callback Hell problem that we often hear:

  • There is no order at all: nested function execution with It is difficult to debug, which is not conducive to maintenance and reading
  • The coupling is too strong: once a certain nesting level is changed, it will affect the execution of the entire callback

Therefore, in order to solve this problem, the community first proposed and implemented Promise, and ES6 wrote it into the language standard to unify its usage.

2.Promise

Promise is a solution to asynchronous programming, which is better than the traditional solution-callback function and Events - more reasonable and powerful. It was born to solve the problems caused by callback functions.

With the Promise object, asynchronous operations can be expressed as a synchronous operation process, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface, making it easier to control asynchronous operations.

So we can change the above callback function to this: (provided that ajax has been wrapped with Promise)

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

By using Promise to handle asynchronous, for example The previous callback functions look clearer, solving the problem of callback hell. The then chain calls of Promise are more acceptable and are in line with our synchronization ideas.

But Promise also has its shortcomings:

  • The internal error of Promise cannot be caught using try catch, you can only use # The second callback of ##then or catch to capture
  • 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 will be executed immediately once it is created and cannot be canceled
I wrote an article before, explaining how to use Promise and its internal implementation principles. Students who don’t understand Promise well can take a look~

From how to use to how to implement a Promise

https://juejin.cn/post/7051364317119119396

3.Generator

Generator function is an asynchronous programming solution provided by ES6, and its syntactic behavior is the same as that of traditional functions completely different. Generator Functions take JavaScript asynchronous programming to a whole new level.

Declaration

is similar to the function declaration, except that there is a star between the

function keyword and the function name number, and the yield expression is used inside the function body to define different internal states (yield means "output" in English).

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

Execution

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

After calling the Generator function, the function is not executed, and what is returned is not the function operation result, but a pointer The pointer object of the internal state, which is

Iterator Object (Iterator Object). Next, the next method of the traverser object must be called to move the pointer to the next state.

So the above callback function can be written like this:

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表示执行结束

Since the traverser object returned by the Generator function, the next internal state will be traversed only by calling the

next method. So it actually provides a function that can pause execution. yieldThe expression is the pause flag.

The running logic of the

next method of the traverser object is as follows.

(1) When encountering the expression

yield, it will suspend the execution of subsequent operations and return the value of the expression immediately following yield The value attribute value of the object.

(2) The next time the

next method is called, execution will continue until the next yield expression is encountered.

(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的区别

相同点:

  • 都能返回语句后面的那个表达式的值
  • 都可以暂停函数执行

区别:

  • 一个函数可以有多个 yield,但是只能有一个 return
  • 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函数执行与普通函数一样,不像Generator函数,需要调用next方法,或使用co模块才能真正执行
  • 语意化更清晰:asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • 适用性更广:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  • 返回值是Promise:async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

async函数

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

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

await表达式

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

  • 如果表达式是 promise 对象, await 返回的是 promise 成功的值

  • 如果表达式是其它值, 直接将此值作为 await 的返回值

  • await后面是Promise对象会阻塞后面的代码,Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果

  • 所以这就是await必须用在async的原因,async刚好返回一个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函数的语法糖。

  • Promise的内部错误使用try catch捕获不到,只能只用then的第二个回调或catch来捕获,而async/await的错误可以用try catch捕获
  • Promise一旦新建就会立即执行,不会阻塞后面的代码,而async函数中await后面是Promise对象会阻塞后面的代码。
  • async函数会隐式地返回一个promise,该promisereosolve值就是函数return的值。
  • 使用async函数可以让代码更加简洁,不需要像Promise一样需要调用then方法来获取返回值,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。

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

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

解题技巧:

  • 无论是then还是catch里的回调内容只要代码正常执行或者正常返回,则当前新的Promise实例为fulfilled状态。如果有报错或返回Promise.reject()则新的Promise实例为rejected状态。
  • fulfilled状态能够触发then回调
  • rejected状态能够触发catch回调
  • 执行async函数,返回的是Promise对象
  • await相当于Promise的then并且同一作用域下await下面的内容全部作为then中回调的内容
  • 异步中先执行微任务,再执行宏任务

【相关推荐:javascript学习教程

The above is the detailed content of A brief analysis of the differences between Promise, Generator and Async. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete