Home > Web Front-end > JS Tutorial > body text

Promise, Generator (generator), async (asynchronous) function usage

一个新手
Release: 2017-10-14 09:16:05
Original
1620 people have browsed it

Promise

What is

Promise is a solution for asynchronous programming. A Promise object represents the final status (completion or failure) of an asynchronous operation and the returned result.

In fact, we have already seen some implementations of Promise in jQuery's ajax. Through Promise, we can convert callbacks into chain calls, which also plays a role in decoupling.

How to use

The basic idea of ​​the Promise interface is to let asynchronous operations return a Promise object

Three states and two ways of change

The Promise object only has Three states.

  • Asynchronous operation "pending"

  • Asynchronous operation "resolved" (also called fulfilled)

  • Asynchronous operation "failed" (rejected)

There are only two ways to change these three states.

  • The asynchronous operation goes from "incomplete" to "completed"

  • The asynchronous operation goes from "incomplete" to "failed".

This change can only occur once. Once the current status changes to "Complete" or "Failed", it means that there will be no new status changes. Therefore, there are only two final results of the Promise object.

The asynchronous operation is successful, the Promise object returns a value, and the status changes to resolved.

The asynchronous operation fails, the Promise object throws an error, and the status changes to rejected.

Generate Promise object

Generate Promise object through new Promise:

var promise = new Promise(function(resolve, reject) {  // 异步操作的代码  if (/* 异步操作成功 */){
    resolve(value)
  } else {
    reject(error)
  }
})
Copy after login

The Promise constructor accepts a function as a parameter. The two parameters of the function are resolve and reject. . They are two functions, provided by the JavaScript engine and do not need to be deployed by yourself.

resolve will change the state of the Promise object from pending to resolved, and reject will change the state of the Promise object from pending to rejected.

The Promise constructor will execute the function immediately after accepting a function

var promise = new Promise(function () {    console.log('Hello World')
})// Hello World
Copy after login

then和catch回调

Promise对象生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。第二个函数是可选的。分别称之为成功回调和失败回调。成功回调接收异步操作成功的结果为参数,失败回调接收异步操作失败报出的错误作为参数。

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('成功')
    }, 3000)
})

promise.then(function (data){    console.log(data)
})// 3s后打印'成功'
Copy after login

catch方法是then(null, rejection)的别名,用于指定发生错误时的回调函数。

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject('失败')
    }, 3000)
})

promise.catch(function (data){    console.log(data)
})// 3s后打印'失败'
Copy after login

Promise.all()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

var p = Promise.all([p1, p2, p3])
Copy after login

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成resolved,p的状态才会变成resolved,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被Rejected,p的状态就变成Rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race()

与Promise.all()类似,不过是只要有一个Promise实例先改变了状态,p的状态就是它的状态,传递给回调函数的结果也是它的结果。所以很形象地叫做赛跑。

Promise.resolve()和Promise.reject()

有时需要将现有对象转为Promise对象,可以使用这两个方法。

Generator(生成器)

是什么

生成器本质上是一种特殊的迭代器(参见本文章系列二之Iterator)。ES6里的迭代器并不是一种新的语法或者是新的内置对象(构造函数),而是一种协议 (protocol)。所有遵循了这个协议的对象都可以称之为迭代器对象。生成器对象由生成器函数返回并且遵守了迭代器协议。具体参见MDN。

怎么用

执行过程

生成器函数的语法为function*,在其函数体内部可以使用yield和yield*关键字。

function* gen(x){  console.log(1)  var y = yield x + 2  console.log(2)  return y
}var g = gen(1)
Copy after login

当我们像上面那样调用生成器函数时,会发现并没有输出。这就是生成器函数与普通函数的不同,它可以交出函数的执行权(即暂停执行)。yield表达式就是暂停标志。

之前提到了生成器对象遵循迭代器协议,所以其实可以通过next方法执行。执行结果也是一个包含value和done属性的对象。

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

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

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

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

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

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。

g.next() 
// 1// { value: 3, done: false }
g.next() 
// 2// { value: undefined, done: true }
Copy after login

for...of遍历

生成器部署了迭代器接口,因此可以用for...of来遍历,不用调用next方法

function *foo() {  yield 1  yield 2  yield 3  return 4
}for (let v of foo()) {  console.log(v)
}// 1// 2// 3
Copy after login

yield*表达式

从语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式。yield后面只能跟迭代器,yield*的功能是将迭代控制权交给后面的迭代器,达到递归迭代的目的

function* foo() {  yield 'a'  yield 'b'
}function* bar() {  yield 'x'  yield* foo()  yield 'y'
}for (let v of bar()) {  console.log(v)
}// x// a// b// y
Copy after login

自动执行

下面是使用Generator函数执行一个真实的异步任务的例子:

var fetch = require('node-fetch')function* gen () {  var url = 'https://api.github.com/users/github'  var result = yield fetch(url)  console.log(result.bio)
}
Copy after login

上面代码中,Generator函数封装了一个异步操作,该操作先读取一个远程接口,然后从JSON格式的数据解析信息。这段代码非常像同步操作,除了加上了yield命令。

执行这段代码的方法如下

var g = gen()
var result = g.next()

result
  .value
  .then(function (data) {    return data.json()
  })
  .then(function (data) {
    g.next(data)
  })
Copy after login

上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法。

可以看到,虽然Generator函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

那么如何自动化异步任务的流程管理呢?

Generator函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点。

  1. 回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权。

  2. Promise对象。将异步操作包装成Promise对象,用then方法交回执行权。

Thunk函数

本节很简略,可能会看不太明白,请参考Thunk 函数的含义和用法

Thunk函数的含义:编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。

JavaScript语言是传值调用,它的Thunk函数含义有所不同。在JavaScript语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

任何函数,只要参数有回调函数,就能写成Thunk函数的形式,可以通过一个Thunk函数转换器来转换。

Thunk函数真正的威力,在于可以自动执行Generator函数。我们可以实现一个基于Thunk函数的Generator执行器,然后直接把Generator函数传入这个执行器即可。

function run(fn) {  
    var gen = fn()  
function next(err, data) {    
    var result = gen.next(data)    
    if (result.done) return
    result.value(next)
  }

  next()
}function* g() {  // ...
}

run(g)
Copy after login

Thunk函数并不是Generator函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise对象也可以做到这一点。

基于Promise对象的自动执行

首先,将方法包装成一个Promise对象(fs是nodejs的一个内置模块)。

var fs = require('fs')var readFile = function (fileName) {  
return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function (error, data) {      
    if (error) reject(error)
      resolve(data)
    })
  })
}var gen = function* () {  
    var f1 = yield readFile('/etc/fstab')  
    var f2 = yield readFile('/etc/shells')  
    console.log(f1.toString())  
    console.log(f2.toString())
}
Copy after login

然后,手动执行上面的Generator函数。

var g = gen()

g.next().value.then(function (data) {
  g.next(data).value.then(function (data) {
    g.next(data)
  })
})
Copy after login

观察上面的执行过程,其实是在递归调用,我们可以用一个函数来实现:

function run(gen){
  var g = gen()  function next(data){
    var result = g.next(data)    if (result.done) return result.value
    result.value.then(function(data){
      next(data)
    })
  }

  next()
}

run(gen)
Copy after login

上面代码中,只要Generator函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。

co模块

co模块是nodejs社区著名的TJ大神写的一个小工具,用于Generator函数的自动执行。

下面是一个Generator函数,用于依次读取两个文件

var gen = function* () {  
var f1 = yield readFile('/etc/fstab')  
var f2 = yield readFile('/etc/shells')  
console.log(f1.toString())  
console.log(f2.toString())
}var co = require('co')
co(gen)
Copy after login

co模块可以让你不用编写Generator函数的执行器。Generator函数只要传入co函数,就会自动执行。co函数返回一个Promise对象,因此可以用then方法添加回调函数。

co(gen).then(function () {  
    console.log('Generator 函数执行完成')
})
Copy after login

co模块的原理:其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。如果数组或对象的成员,全部都是Promise对象,也可以使用co(co v4.0版以后,yield命令后面只能是Promise对象,不再支持Thunk函数)。

async(异步)函数

是什么

async函数属于ES7。目前,它仍处于提案阶段,但是转码器Babel和regenerator都已经支持。async函数可以说是目前异步操作最好的解决方案,是对Generator函数的升级和改进。

怎么用

1)语法

async函数声明定义了异步函数,它会返回一个AsyncFunction对象。和普通函数一样,你也可以定义一个异步函数表达式。

调用异步函数时会返回一个promise对象。当这个异步函数成功返回一个值时,将会使用promise的resolve方法来处理这个返回值,当异步函数抛出的是异常或者非法值时,将会使用promise的reject方法来处理这个异常值。

异步函数可能会包括await表达式,这将会使异步函数暂停执行并等待promise解析传值后,继续执行异步函数并返回解析值。

注意:await只能用在async函数中。

前面依次读取两个文件的代码写成async函数如下:

var asyncReadFile = async function (){  
    var f1 = await readFile('/etc/fstab')  
    var f2 = await readFile('/etc/shells')  
    console.log(f1.toString())  
    console.log(f2.toString())
}
Copy after login

async函数将Generator函数的星号(*)替换成了async,将yield改为了await。

2)async函数的改进

async函数对Generator函数的改进,体现在以下三点。

(1)内置执行器。Generator函数的执行必须靠执行器,所以才有了co函数库,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

var result = asyncReadFile()
Copy after login

(2)更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。co函数库约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以跟Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

3)基本用法

同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

function resolveAfter2Seconds (x) {  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  })
}async function add1 (x) {  var a = resolveAfter2Seconds(20)  var b = resolveAfter2Seconds(30)  return x + await a + await b
}

add1(10).then(v => {  console.log(v)  
})// 2s后打印60async function add2 (x) {  var a = await resolveAfter2Seconds(20)  var b = await resolveAfter2Seconds(30)  return x + a + b
}

add2(10).then(v => {  console.log(v)
})// 4s后打印60
Copy after login

4)捕获错误

可以使用.catch回调捕获错误,也可以使用传统的try...catch。

async function myFunction () {  
    try {    
    await somethingThatReturnsAPromise()
  } catch (err) {    
  console.log(err)
  }
}// 另一种写法async function myFunction () {  await somethingThatReturnsAPromise()
  .catch(function (err) {    console.log(err)
  }
}
Copy after login

5)并发的异步操作

let foo = await getFoo()let bar = await getBar()
Copy after login

多个await命令后面的异步操作会按顺序完成。如果不存在继发关系,最好让它们同时触发。上面的代码只有getFoo完成,才会去执行getBar,这样会比较耗时。如果这两个是独立的异步操作,完全可以让它们同时触发。

// 写法一let [foo, bar] = await Promise.all([getFoo(), getBar()])
// 写法二let fooPromise = getFoo()let barPromise = getBar()let foo = await fooPromiselet bar = await barPromise
Copy after login

The above is the detailed content of Promise, Generator (generator), async (asynchronous) function usage. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template