当前位置: 首页 > news >正文

Node.js:ES6 模块化 Promise

Node.js:ES6 模块化 & Promise

    • ES6 模块化
      • 默认导入导出
      • 按需导入导出
    • Promise
      • 构造
      • 状态
      • then
      • catch
      • all
      • race
      • async
      • await


ES6 模块化

Node.js中,遵循的是CommonJS的模块化规范,使用require方法导入模块,使用moudule.exports导出模块。

JavaScript中,存在很多模块化的规范,后端常用CommonJS,前端常用AMDCMD等。

这导致JavaScript没有一个统一的方式完成模块化,增加了学习成本,为此推出了统一的ES6模块化规范,不论前后端,只要是JavaScript都可以支持这套规范。

规定:

  1. 每个.js文件都是一个独立的模块
  2. 导入模块使用import
  3. 共享模块成员使用export

想要在Node.js中使用ES6模块化规范,需要在配置文件package.json中添加一个键值对:

"type": "module"

这样就可以支持ES6模块化了。

测试:

  • test.js
console.log("hello world")
  • main.js
import './test.js'

main.js中,直接import另一个文件,就会把被导入文件内部的所有代码执行一次

运行main.js输出结果:

hello world

但是直接import是拿不到模块内部的变量的,这需要在模块内部进行导出操作。


默认导入导出

默认导出语法:

export default {导出变量1,导出变量2,......
}

默认导入语法:

import 变量名 from '模块'

这相当于const 变量名 = require('模块'),只要在模块内部通过默认导出,就可以在外部导入模块时,拿到指定变量。

  • test.js
let n1 = 10
let n2 = 20
function hello() {console.log('hello world')
}export default {n1,hello
}

test.js中,把变量n1和函数hello进行了默认导出。

  • main.js
import test_module from './test.js'
console.log(test_module)

main.js中,默认导入了test.js,结果导入在test_module中。

输出结果:

{n1: 10, hello: ƒ}

拿到了n1hello两个值,但是没有拿到n2,因为n2没有被导出。


按需导入导出

以上的所有功能,都可以通过require进行完成,ES6还支持按需的导入导出,只导入需要的变量。

按需导出:

export 变量

在声明变量时,可以添加export关键字,那么该变量就可以被按需导出,只有在用户指定的情况下,该变量才会被导出。

按需导入:

import { 变量1, 变量2 } from '模块'

导入时,使用{}进行按需导入,把要导入的变量名写在内部,表示只导入这些变量。

  • test.js
let n1 = 10
export let n2 = 20export function bye() {console.log('bye bye')
}export default {n1
}

默认导出n1,按需导出n2bye

  • main.js
import {n2, bye} from './test.js'console.log(n2)
bye()

按需导入n2bye

输出结果:

20
bye bye

成功导入了两个按需导出的变量,但是没有导入n1,因为n1是默认导出的。

main.js进行默认导入:

import test_moudle from './test.js'
console.log(test_moudle)

输出结果:

{n1: 10}

此时只能拿到n1,因为n2bey是按需导出的,默认导出不会导出按需导出的变量

另外的,导入时可以同时执行按需导出和默认导出:

import test_moudle, { n2 } from './test.js'

以上代码默认导出了test_moudle,并按需导出了n2,这个过程中bye没有被导出,如果想要导出bey,只需要加到{}内部:

import test_moudle, { n2, bye } from './test.js'

对于按需导出的变量,{}内部的变量名必须和模块内部的变量名完全一致。但是这有可能导致命名冲突。

示例:

import { n2, bye } from './test.js'
const n2 = 1

在按需导入时,导入了n2变量,但是当前模块也有n2变量,这就导致命名冲突。

在按需导入时,可以进行变量重命名:

import { 变量名 as 新名 } from '模块'

示例:

import { n2 as test_n2, bye } from './test.js'
const n2 = 1

按需导入n2时,将n2重命名为test_n2,这样就不会发生命名冲突了。


Promise

由于JavaScript是单线程异步的模型,如果想让函数按照指定顺序执行,常常采用回调函数的形式。

比如以下三个函数:

function asyncOperation1(next) {console.log('Operation 1 completed')next()
}function asyncOperation2(next) {console.log('Operation 2 completed')next()
}function asyncOperation3(next) {console.log('Operation 3 completed')next()
}

每个函数都接受一个next回调,现在希望按顺序依次执行这三个函数:

asyncOperation1(() => {asyncOperation2(() => {asyncOperation3(() => {console.log('All operations completed');});});
});

此时就会变成一个多级嵌套的结构,虽然可以解决问题,但是这样的代码很不优雅,看起来也很费劲。

ES6版本后,可以使用Promise解决这种多级嵌套结构,让函数之间解耦。

使用Promise优化后,代码大致如下:

asyncOperation1().then(() => asyncOperation2()).then(() => asyncOperation3()).then(() => {console.log('All operations completed');})

可以看到,asyncOperation2asyncOperation3这两个函数,都在同一级缩进中,就算后面再加多少个函数,都只会在这样的同一级缩进中:

asyncOperation1().then(() => asyncOperation2()).then(() => asyncOperation3()).then(() => asyncOperation4()).then(() => asyncOperation5()).then(() => asyncOperation6()).then(() => asyncOperation7()).then(() => {console.log('All operations completed');})

这样可以保证1 - 7按顺序调用,并且避免了多级嵌套的情况。


构造

Promise的功能,更多是完成异步任务,其可以标识一个函数的执行状态以及执行结果,并在一个异步任务接触后,进行下一步操作。

Promise的本质是一个构造函数,格式如下:

Promise((resolve, reject) => {// 函数体
})

该构造接收一个函数,函数内包含两个参数:

  • resolve:表示函数执行成功
  • reject:表示函数执行失败

示例:

const p = new Promise((resolve, reject) => {let n = Math.floor(Math.random() * 10) + 1if (n % 2 == 0)resolve()elsereject()
})

这是一个随机函数,生成1 - 10的随机数n,依据n % 2的结果,选择调用不同的函数resolvereject,这样就会产生两种不同的状态。

这个Promise函数构造出的对象,可以使用.then方法传入resolvereject对应的回调函数,如果Promise的状态为resolve就执行第一个函数,如果状态为reject就执行第二个函数。

示例:

p.then(() => {console.log("success!")
}, () => {console.log("fail!")
})

p是刚才构造出的对象,通过then的两个参数,传入回调函数,并且.then会等待到p构造函数内部的函数执行完毕后,再依据其状态选择调用两个函数之一。

现在就可以尝试优化之前的asyncOperation函数:

function asyncOperation1() {return new Promise((resolve) => {console.log('Operation 1 completed');resolve();});
}

asyncOperation1要调用.then方法,来指定下一个执行的函数,因此该函数的返回值必须是一个Promise对象,在对象内部,调用resolve就是在调用下一个函数。

以上Promise构造函数没有用到reject这个参数,因为它不需要分情况回调不同的函数,所以只需要一个参数即可。

类似的,后两个函数如下:

function asyncOperation2() {return new Promise((resolve) => {console.log('Operation 2 completed');resolve();});
}function asyncOperation3() {return new Promise((resolve) => {console.log('Operation 3 completed');resolve();});
}

这样就形成了一个链式调用,上一个函数返回一个Promise对象,并调用.then方法传入下一个函数。这样就可以让函数按顺序执行,且可以一直追加.then方法来拓展函数调用链条,避免嵌套式回调。

asyncOperation1().then(() => asyncOperation2()).then(() => asyncOperation3()).then(() => {console.log('All operations completed');})

状态

Promise的功能,远远不止处理回调,其还有很多其它功能。

示例:

const p = new Promise((resolve, reject) => {throw("err");resolve()
})p.then(() => {console.log("success!")console.log(p)
}, () => {console.log("fail!")console.log(p)
})

Promise中,只调用了resolve方法,但是在调用该方法之前,throw了一个异常,最后调用的不是resolve而是reject

输出结果:

fail!
Promise {[[PromiseState]]: 'rejected', [[PromiseResult]]: 'err', Symbol(async_id_symbol): 71, Symbol(trigger_async_id_symbol): 36}

可以看到,输出结果中,有一个[[PromiseState]]属性,这个属性就是记录当前的Promise状态,最终根据这状态,来决定调用resolve还是reject

[[PromiseResult]]存储着上一个函数的输出结果,这个可以直接在调用resolve()reject()时作为参数传入。

示例:

const p = new Promise((resolve, reject) => {resolve('OK!')
})p.then(() => {console.log("success!")console.log(p)
}, () => {console.log("fail!")console.log(p)
})

这一次只调用了resolve函数,并且调用时传入了一个参数OK!,输出结果:

success!
Promise {[[PromiseState]]: 'fulfilled',[[PromiseResult]]: 'OK!',Symbol(async_id_symbol): 71,Symbol(trigger_async_id_symbol): 36}

这一次的状态为fulfilled表示成功,结果为OK!

Promise分为以下三种状态:

  1. pending:默认状态,表示还不确定成功或失败
  2. fulfilled:成功状态
  3. rejected:失败状态

当一个Promise对象创建时,为默认状态,如果调用了resolve()就会变为fulfilled状态,调用reject()就会变为rejected状态。

Promise对象调用then方法时,依据当前的[[PromiseState]]来决定下一个函数调用resolve还是reject

切换状态的方法:

  1. resolve:从pending变为fulfilled
  2. reject:从pending变为rejected
  3. thorw:抛出一个异常,从pending变为rejected

要注意的是,一个Promise对象的状态只会切换一次,并且只能从pending切换为fulfilledrejected两者之一,后两者之间不能随意切换。

示例:

const p = new Promise((resolve, reject) => {resolve('OK!')reject('error')
})

这个函数中,最后p的状态为fulfilled,因为先执行了resolve,从pending切换为fulfilled状态。第二次调用rejectfulfilled不能再切换为rejected状态了。


then

Promise构造中,要求用户传入一个函数:

let p1 = new Promise((resolve, reject) => {resolve('p1 OK!')
})

此时整个(resolve, reject) => {}函数会立刻执行一次,有人就有疑问了,resolvereject不是两个回调函数吗?如果没有通过.then传入回调函数,p1怎么知道自己要回调哪个函数?

刚讲解了Promise对象的状态,可以得知resolvereject的本质是在改变Promise对象的状态,其实根本就没有去调用函数。

then的作用,就是等待构造函数内部的函数执行完毕,再依据状态进行下一步操作。

示例:

let p1 = new Promise((resolve, reject) => {setTimeout(()=>{resolve('p1 OK!')}, 10000)
})console.log(p1)

p1中设置了一个延时,10s之后把自己的状态设为成功。但是由于此时构成异步,console.log(p1)会比 resolve('p1 OK!')先执行,输出结果:

Promise {[[PromiseState]]: 'pending',[[PromiseResult]]: undefined, Symbol(async_id_symbol): 71, Symbol(trigger_async_id_symbol): 36}

此时输出p1的状态就是pending,表示还不确定状态,结果也为undefined

修改代码:

let p1 = new Promise((resolve, reject) => {setTimeout(()=>{resolve('p1 OK!')}, 10000)
})p1.then(()=>{console.log(p1)
})

此时p1.then,会等待到p1的状态确定后再执行,保证调用的顺序。

输出结果:

Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'p1 OK!', Symbol(async_id_symbol): 71, Symbol(trigger_async_id_symbol): 36}

这次输出成功了,因为.then会等到Promise状态确定后,才执行内部的回调函数。

一个Promise也可以绑定多个then,一旦Promise对象的状态完成切换,所有的then都会被执行


catch

catch方法,可以指定一个错误处理函数reject,当使用多个.then调用Promise时,可以使用.catch处理所有Promise发生的错误。

示例:

function func1(){return new Promise((resolve, reject) => {console.log('func1 successed')resolve('OK!')})
}function func2(){return new Promise((resolve, reject) => {console.log('func2 successed')resolve('OK!')})
}function func3(){return new Promise((resolve, reject) => {console.log('func3 error')reject('error')})
}function func4(){return new Promise((resolve, reject) => {console.log('func4 successed')resolve('OK!')})
}function funcErr() {console.log("something err happend")
}

这四个函数都返回一个Promise对象,最后一个函数funcErr用于处理错误,那么调用逻辑就写为:

func1().then(func2, funcErr).then(func3, funcErr).then(func4, funcErr)

输出结果:

func1 successed
func2 successed
func3 error
something err happend

输出完func3后,代码出现错误,此时触发funcErr,不再执行func4

以上代码有两个优化点:

  1. 所有函数都使用funcErr处理错误,能不能一次性给他们指定错误处理函数
  2. func3出错后,func4不再执行了,如果在funcErr内部修理好了错误,能不能继续执行func4

以上问题都可以通过cache解决。

func1().then(func2).then(func3).then(func4).catch(funcErr)

在所有then的末尾,添加一个catch,传入funcErr参数,只要有任意一个函数调用出错,都会执行catch内部的函数。

输出结果:

func1 successed
func2 successed
func3 error
something err happend

可以看到,catch捕捉到了func3产生的错误,但是func4依然没有执行。

为此,可以把.cache的位置修改一下:

func1().then(func2).then(func3).catch(funcErr).then(func4)

.catch执行完毕后,会返回一个Promise对象,状态为成功,这样就可以继续调用func4了:

输出结果:

func1 successed
func2 successed
func3 error
something err happend
func4 successed

all

allPromise的一个方法,其传入一个Promise对象的数组。

语法:

Promise.all([Promise, Promise ...])

如果所有Promise对象执行成功,那么all返回一个成功的Promise对象,如果任意一个Promise对象执行失败,返回一个失败的Promise对象。

这里所谓的成功或失败的Promise对象,其实就是对象的状态分别为fulfilledrejected

示例:

let p1 = new Promise((resolve, reject) => {resolve('p1 OK!')
})let p2 = new Promise((resolve, reject) => {resolve('p2 OK!')
})let p3 = new Promise((resolve, reject) => {resolve('p3 OK!')
})let ret = Promise.all([p1, p2, p3])ret.then(() => {console.log(ret)
})

此处定义了三个Promise,最后使用Promise.all([p1, p2, p3]),得到一个Promise对象ret,输出这个ret

此处注意,最后不能直接console.log(ret),要放进.then中,因为定义p1p2p3的过程是异步的,执行console.log(ret)时,前三者可能还没有执行完。

输出结果:

在这里插入图片描述

收到的Promise对象中,状态为成功,结果是一个数组,分别是p1 p2 p3的输出结果。

修改代码:

let p1 = new Promise((resolve, reject) => {resolve('p1 OK!')
})let p2 = new Promise((resolve, reject) => {reject('p2 err!')
})let p3 = new Promise((resolve, reject) => {resolve('p3 OK!')
})let ret = Promise.all([p1, p2, p3])ret.catch(error => {console.log(ret)
});

此处p2改为错误的Promise,并且最后变为ret.catch,如果不捕获错误的话会报错。

输出结果:

在这里插入图片描述

输出结果是p2的内容。

返回值:

  1. 如果数组中所有的Promise成功,返回的Promise也成功,结果是一个数组,包含所有Promise的结果
  2. 如果数组中任意一个Promise失败,返回的Promise也失败,结果是失败的那个元素的结果
  3. 如果数组中多个Promise失败,返回的Promise也失败,结果是最早失败的那个元素的结果

race

race也是Promise的一个方法,其传入一个Promise对象的数组。

语法:

Promise.race([Promise, Promise ...])

race意为赛跑,当数组中任意一个Promise确定成功或失败,race直接返回这个Promise

其实就是得到执行速度最快的Promise


async

async是一个关键字,被async修饰的函数,返回值会变成一个Promise对象。

语法:

async function name{
}

只需要在function前增加一个async关键字即可。

返回值如下:

  1. 如果返回值是一个非Promise类型,或者没有返回值,那么返回一个状态为成功的Promise对象
  2. 如果返回值是一个Promise类型,原先是什么就返回什么
  3. 如果函数抛异常了,返回一个状态为失败的Promise对象

示例:

async function test()
{return "hello"
}console.log(test())

输出结果:

Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'hello', Symbol(async_id_symbol): 71, Symbol(trigger_async_id_symbol): 36}

此时返回值变为了Promise对象,类型是成功,原本的返回值hello存储在了[[PromiseResult]]中。

测试抛异常:

async function test()
{throw "error"
}console.log(test())

输出结果:

Promise {[[PromiseState]]: 'rejected', [[PromiseResult]]: 'error',Symbol(async_id_symbol): 71, Symbol(trigger_async_id_symbol): 36}

得到的返回值就是一个rejectedPromise对象,抛出的异常存储在了[[PromiseResult]]中。


await

await是一个表达式,可以快速拿到Promise存储的值。await必须写在async函数中,但是async函数内可以没有await

await右侧接收一个表达式:

  1. 如果表达式是成功的Promise对象,返回对象存储的值
  2. 如果表达式是失败的Promise对象,抛出一个异常
  3. 如果表达式不是Promise对象,直接返回表达式值

示例:


async function test()
{let p = new Promise((resolve, reject)=>{resolve('OK!')})console.log(await p)console.log(await 'hello')
}
test()

输出结果:

OK!
hello


http://www.mrgr.cn/news/65132.html

相关文章:

  • CentOS 7 安装 ntp,自动校准系统时间
  • 算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
  • 智慧汇聚:十款企业培训工具打造学习型企业
  • 何时用““传参?
  • Java毕业设计-基于微信小程序的校园二手物品交易系统的实现(V2.0)
  • 珠海盈致mes系统在来料检验管理的优缺点
  • Intel nuc x15 重装系统步骤和注意事项(LAPKC71F、LAPKC71E、LAPKC51E)
  • XPath 实例
  • 哪些因素导致了 MySQL 数据库的延时呢?
  • Vuex的基本使用
  • Nginx 在中小企业的初级应用实操指南
  • C语言 | Leetcode 题解之第535题TinyURL的加密与解密
  • Thumb 汇编指令集,Thumb 指令编码方式,编译 Thumb 汇编代码
  • 软件平台系统稳定性规范
  • PHP JSON 教程
  • 国产操作系统重新安装软件商店
  • CSS 计数器:深入解析与高级应用
  • 21.网工入门篇--------介绍下SDN与NFV的概述
  • Spring 函数式端点详解
  • 【Linux 26】应用层协议 - HTTP
  • 工作:三菱IQ-R PLC的SFC程序编写方式及代码模拟仿真
  • 项目开发管理之开发、测试到上线
  • 英语四六级/考研英语资料迅雷网盘免费分享
  • 嵌入式实验1-软件配置+STM32最小系统+LED灯交替闪烁
  • koa + sequelize做距离计算(MySql篇)
  • MyBatis-Plus条件构造器:构建安全、高效的数据库查询