Node.js:ES6 模块化 & Promise

Node.js:ES6 模块化 & Promise


ES6 模块化

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

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

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

规定:

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

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

javascript 复制代码
"type": "module"

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

测试:

  • test.js
javascript 复制代码
console.log("hello world")
  • main.js
javascript 复制代码
import './test.js'

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

运行main.js输出结果:

javascript 复制代码
hello world

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


默认导入导出

默认导出语法:

javascript 复制代码
export default {
  导出变量1,
  导出变量2,
  ......
}

默认导入语法:

javascript 复制代码
import 变量名 from '模块'

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

  • test.js
javascript 复制代码
let n1 = 10
let n2 = 20
function hello() {
    console.log('hello world')
}

export default {
  n1,
  hello
}

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

  • main.js
javascript 复制代码
import test_module from './test.js'
console.log(test_module)

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

输出结果:

javascript 复制代码
{n1: 10, hello: ƒ}

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


按需导入导出

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

按需导出:

javascript 复制代码
export 变量

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

按需导入:

javascript 复制代码
import { 变量1, 变量2 } from '模块'

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

  • test.js
javascript 复制代码
let n1 = 10
export let n2 = 20

export function bye() {
    console.log('bye bye')
}

export default {
  n1
}

默认导出n1,按需导出n2bye

  • main.js
javascript 复制代码
import {n2, bye} from './test.js'

console.log(n2)
bye()

按需导入n2bye

输出结果:

javascript 复制代码
20
bye bye

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

main.js进行默认导入:

javascript 复制代码
import test_moudle from './test.js'
console.log(test_moudle)

输出结果:

javascript 复制代码
{n1: 10}

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

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

javascript 复制代码
import test_moudle, { n2 } from './test.js'

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

javascript 复制代码
import test_moudle, { n2, bye } from './test.js'

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

示例:

javascript 复制代码
import { n2, bye } from './test.js'
const n2 = 1

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

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

javascript 复制代码
import { 变量名 as 新名 } from '模块'

示例:

javascript 复制代码
import { n2 as test_n2, bye } from './test.js'
const n2 = 1

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


Promise

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

比如以下三个函数:

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回调,现在希望按顺序依次执行这三个函数:

javascript 复制代码
asyncOperation1(() => {
  asyncOperation2(() => {
    asyncOperation3(() => {
      console.log('All operations completed');
    });
  });
});

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

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

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

javascript 复制代码
asyncOperation1()
  .then(() => asyncOperation2())
  .then(() => asyncOperation3())
  .then(() => {
    console.log('All operations completed');
  })

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

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

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


构造

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

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

javascript 复制代码
Promise((resolve, reject) => {
	// 函数体
})

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

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

示例:

javascript 复制代码
const p = new Promise((resolve, reject) => {
    let n = Math.floor(Math.random() * 10) + 1
    if (n % 2 == 0)
        resolve()
    else
        reject()
})

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

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

示例:

javascript 复制代码
p.then(() => {
    console.log("success!")
}, () => {
    console.log("fail!")
})

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

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

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

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

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

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

javascript 复制代码
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方法来拓展函数调用链条,避免嵌套式回调。

javascript 复制代码
asyncOperation1()
  .then(() => asyncOperation2())
  .then(() => asyncOperation3())
  .then(() => {
    console.log('All operations completed');
  })

状态

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

示例:

javascript 复制代码
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

输出结果:

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

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

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

示例:

javascript 复制代码
const p = new Promise((resolve, reject) => {
    resolve('OK!')
})

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

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

javascript 复制代码
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两者之一,后两者之间不能随意切换。

示例:

javascript 复制代码
const p = new Promise((resolve, reject) => {
    resolve('OK!')
    reject('error')
})

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


then

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

javascript 复制代码
let p1 = new Promise((resolve, reject) => {
    resolve('p1 OK!')
})

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

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

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

示例:

javascript 复制代码
let p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('p1 OK!')
    }, 10000)
})

console.log(p1)

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

javascript 复制代码
Promise {
	[[PromiseState]]: 'pending',
	[[PromiseResult]]: undefined, 
	Symbol(async_id_symbol): 71, 
	Symbol(trigger_async_id_symbol): 36
	}

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

修改代码:

javascript 复制代码
let p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('p1 OK!')
    }, 10000)
})

p1.then(()=>{
    console.log(p1)
})

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

输出结果:

javascript 复制代码
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发生的错误。

示例:

javascript 复制代码
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用于处理错误,那么调用逻辑就写为:

javascript 复制代码
func1()
    .then(func2, funcErr)
    .then(func3, funcErr)
    .then(func4, funcErr)

输出结果:

javascript 复制代码
func1 successed
func2 successed
func3 error
something err happend

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

以上代码有两个优化点:

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

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

javascript 复制代码
func1()
    .then(func2)
    .then(func3)
    .then(func4)
    .catch(funcErr)

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

输出结果:

javascript 复制代码
func1 successed
func2 successed
func3 error
something err happend

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

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

javascript 复制代码
func1()
    .then(func2)
    .then(func3)
    .catch(funcErr)
    .then(func4)

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

输出结果:

javascript 复制代码
func1 successed
func2 successed
func3 error
something err happend
func4 successed

all

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

语法:

javascript 复制代码
Promise.all([Promise, Promise ...])

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

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

示例:

javascript 复制代码
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的输出结果。

修改代码:

javascript 复制代码
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对象的数组。

语法:

javascript 复制代码
Promise.race([Promise, Promise ...])

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

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


async

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

语法:

javascript 复制代码
async function name{
}

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

返回值如下:

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

示例:

javascript 复制代码
async function test()
{
    return "hello"
}

console.log(test())

输出结果:

javascript 复制代码
Promise {
	[[PromiseState]]: 'fulfilled', 
	[[PromiseResult]]: 'hello', 
	Symbol(async_id_symbol): 71, 
	Symbol(trigger_async_id_symbol): 36
	}

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

测试抛异常:

javascript 复制代码
async function test()
{
    throw "error"
}

console.log(test())

输出结果:

javascript 复制代码
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对象,直接返回表达式值

示例:

javascript 复制代码
async function test()
{
    let p = new Promise((resolve, reject)=>{
        resolve('OK!')
    })

    console.log(await p)
    console.log(await 'hello')
}
test()

输出结果:

javascript 复制代码
OK!
hello

相关推荐
渊兮兮几秒前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
鑫宝Code几秒前
【TS】TypeScript中的接口(Interface):对象类型的强大工具
前端·javascript·typescript
和你一起去月球1 分钟前
TypeScript - 函数(下)
javascript·git·typescript
_Legend_King7 分钟前
vue3 + elementPlus 日期时间选择器禁用未来及过去时间
javascript·vue.js·elementui
余生H8 分钟前
transformer.js(三):底层架构及性能优化指南
javascript·深度学习·架构·transformer
景天科技苑22 分钟前
【vue3+vite】新一代vue脚手架工具vite,助力前端开发更快捷更高效
前端·javascript·vue.js·vite·vue项目·脚手架工具
石小石Orz31 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
小行星12533 分钟前
前端预览pdf文件流
前端·javascript·vue.js
join834 分钟前
解决vue-pdf的签章不显示问题
javascript·vue.js·pdf
小行星12540 分钟前
前端把dom页面转为pdf文件下载和弹窗预览
前端·javascript·vue.js·pdf