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,前端常用AMD、CMD等。
这导致JavaScript没有一个统一的方式完成模块化,增加了学习成本,为此推出了统一的ES6模块化规范,不论前后端,只要是JavaScript都可以支持这套规范。
规定:
- 每个
.js文件都是一个独立的模块 - 导入模块使用
import - 共享模块成员使用
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: ƒ}
 
拿到了n1和hello两个值,但是没有拿到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,按需导出n2和bye。
main.js:
import {n2, bye} from './test.js'console.log(n2)
bye()
 
按需导入n2和bye。
输出结果:
20
bye bye
 
成功导入了两个按需导出的变量,但是没有导入n1,因为n1是默认导出的。
main.js进行默认导入:
import test_moudle from './test.js'
console.log(test_moudle)
 
输出结果:
{n1: 10}
 
此时只能拿到n1,因为n2和bey是按需导出的,默认导出不会导出按需导出的变量。
另外的,导入时可以同时执行按需导出和默认导出:
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');})
 
可以看到,asyncOperation2和asyncOperation3这两个函数,都在同一级缩进中,就算后面再加多少个函数,都只会在这样的同一级缩进中:
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的结果,选择调用不同的函数resolve或reject,这样就会产生两种不同的状态。
这个Promise函数构造出的对象,可以使用.then方法传入resolve和reject对应的回调函数,如果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分为以下三种状态:
pending:默认状态,表示还不确定成功或失败fulfilled:成功状态rejected:失败状态
当一个Promise对象创建时,为默认状态,如果调用了resolve()就会变为fulfilled状态,调用reject()就会变为rejected状态。
当Promise对象调用then方法时,依据当前的[[PromiseState]]来决定下一个函数调用resolve还是reject。
切换状态的方法:
resolve:从pending变为fulfilledreject:从pending变为rejectedthorw:抛出一个异常,从pending变为rejected
要注意的是,一个Promise对象的状态只会切换一次,并且只能从pending切换为fulfilled或rejected两者之一,后两者之间不能随意切换。
示例:
const p = new Promise((resolve, reject) => {resolve('OK!')reject('error')
})
 
这个函数中,最后p的状态为fulfilled,因为先执行了resolve,从pending切换为fulfilled状态。第二次调用reject,fulfilled不能再切换为rejected状态了。
then
在Promise构造中,要求用户传入一个函数:
let p1 = new Promise((resolve, reject) => {resolve('p1 OK!')
})
 
此时整个(resolve, reject) => {}函数会立刻执行一次,有人就有疑问了,resolve和reject不是两个回调函数吗?如果没有通过.then传入回调函数,p1怎么知道自己要回调哪个函数?
刚讲解了Promise对象的状态,可以得知resolve和reject的本质是在改变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。
以上代码有两个优化点:
- 所有函数都使用
funcErr处理错误,能不能一次性给他们指定错误处理函数 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
all是Promise的一个方法,其传入一个Promise对象的数组。
语法:
Promise.all([Promise, Promise ...])
 
如果所有Promise对象执行成功,那么all返回一个成功的Promise对象,如果任意一个Promise对象执行失败,返回一个失败的Promise对象。
这里所谓的成功或失败的Promise对象,其实就是对象的状态分别为fulfilled和rejected。
示例:
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中,因为定义p1、p2、p3的过程是异步的,执行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的内容。
返回值:
- 如果数组中所有的
Promise成功,返回的Promise也成功,结果是一个数组,包含所有Promise的结果 - 如果数组中任意一个
Promise失败,返回的Promise也失败,结果是失败的那个元素的结果 - 如果数组中多个
Promise失败,返回的Promise也失败,结果是最早失败的那个元素的结果 
race
race也是Promise的一个方法,其传入一个Promise对象的数组。
语法:
Promise.race([Promise, Promise ...])
 
race意为赛跑,当数组中任意一个Promise确定成功或失败,race直接返回这个Promise。
其实就是得到执行速度最快的Promise。
async
async是一个关键字,被async修饰的函数,返回值会变成一个Promise对象。
语法:
async function name{
}
 
只需要在function前增加一个async关键字即可。
返回值如下:
- 如果返回值是一个非
Promise类型,或者没有返回值,那么返回一个状态为成功的Promise对象 - 如果返回值是一个
Promise类型,原先是什么就返回什么 - 如果函数抛异常了,返回一个状态为失败的
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}
 
得到的返回值就是一个rejected的Promise对象,抛出的异常存储在了[[PromiseResult]]中。
await
await是一个表达式,可以快速拿到Promise存储的值。await必须写在async函数中,但是async函数内可以没有await。
await右侧接收一个表达式:
- 如果表达式是成功的
Promise对象,返回对象存储的值 - 如果表达式是失败的
Promise对象,抛出一个异常 - 如果表达式不是
Promise对象,直接返回表达式值 
示例:
async function test()
{let p = new Promise((resolve, reject)=>{resolve('OK!')})console.log(await p)console.log(await 'hello')
}
test()
 
输出结果:
OK!
hello
 
