0

0

Promise,Generator(生成器),async(异步)函数的用法

一个新手

一个新手

发布时间:2017-10-14 09:16:05

|

1722人浏览过

|

来源于php中文网

原创

promise

是什么

promise是异步编程的一种解决方案。promise对象表示了异步操作的最终状态(完成或失败)和返回的结果。

其实我们在jQuery的ajax中已经见识了部分Promise的实现,通过Promise,我们能够将回调转换为链式调用,也起到解耦的作用。

怎么用

Promise接口的基本思想是让异步操作返回一个Promise对象

三种状态和两种变化途径

Promise对象只有三种状态。

  • 异步操作“未完成”(pending)

  • 异步操作“已完成”(resolved,又称fulfilled)

  • 异步操作“失败”(rejected)

    HueBit AI
    HueBit AI

    一站式AI艺术创作工具

    下载

这三种的状态的变化途径只有两种。

  • 异步操作从“未完成”到“已完成”

  • 异步操作从“未完成”到“失败”。

这种变化只能发生一次,一旦当前状态变为“已完成”或“失败”,就意味着不会再有新的状态变化了。因此,Promise对象的最终结果只有两种。

异步操作成功,Promise对象传回一个值,状态变为resolved。

异步操作失败,Promise对象抛出一个错误,状态变为rejected。

生成Promise对象

通过new Promise来生成Promise对象:

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

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve会将Promise对象的状态从pending变为resolved,reject则是将Promise对象的状态从pending变为rejected。

Promise构造函数接受一个函数后会立即执行这个函数

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

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后打印'成功'

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

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

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

Promise.all()

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

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

上面代码中,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)

当我们像上面那样调用生成器函数时,会发现并没有输出。这就是生成器函数与普通函数的不同,它可以交出函数的执行权(即暂停执行)。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 }

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

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

自动执行

下面是使用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)
}

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

执行这段代码的方法如下

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

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

上面代码中,首先执行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)

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())
}

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

var g = gen()

g.next().value.then(function (data) {
  g.next(data).value.then(function (data) {
    g.next(data)
  })
})

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

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)

上面代码中,只要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)

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

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

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())
}

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

2)async函数的改进

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

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

var result = asyncReadFile()

(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

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)
  }
}

5)并发的异步操作

let foo = await getFoo()let bar = await getBar()

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

6

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

6

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

8

2026.02.28

Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

14

2026.02.27

Golang 高级特性与最佳实践:提升代码艺术
Golang 高级特性与最佳实践:提升代码艺术

本专题深入剖析 Golang 的高级特性与工程级最佳实践,涵盖并发模型、内存管理、接口设计与错误处理策略。通过真实场景与代码对比,引导从“可运行”走向“高质量”,帮助构建高性能、可扩展、易维护的优雅 Go 代码体系。

17

2026.02.27

Golang 测试与调试专题:确保代码可靠性
Golang 测试与调试专题:确保代码可靠性

本专题聚焦 Golang 的测试与调试体系,系统讲解单元测试、表驱动测试、基准测试与覆盖率分析方法,并深入剖析调试工具与常见问题定位思路。通过实践示例,引导建立可验证、可回归的工程习惯,从而持续提升代码可靠性与可维护性。

2

2026.02.27

漫蛙app官网链接入口
漫蛙app官网链接入口

漫蛙App官网提供多条稳定入口,包括 https://manwa.me、https

130

2026.02.27

deepseek在线提问
deepseek在线提问

本合集汇总了DeepSeek在线提问技巧与免登录使用入口,助你快速上手AI对话、写作、分析等功能。阅读专题下面的文章了解更多详细内容。

8

2026.02.27

AO3官网直接进入
AO3官网直接进入

AO3官网最新入口合集,汇总2026年可用官方及镜像链接,助你快速稳定访问Archive of Our Own平台。阅读专题下面的文章了解更多详细内容。

208

2026.02.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号