即使是 async / await 也是基于 Promise 的,任何的异步过程我觉得都应该用 Promise 做抽象。
Promise 可以被理解为一种状态机,或者函数式编程里的容器类型。
# 状态机解释 ↵
Promise 的抽象性源于它的命名:承诺。
Promise 是一台蕴含着异步过程以及结果的状态机
Promise 的状态机的状态有且仅有这三种:
- Pendding 态
- Resolved 态
- Rejected 态
并且只有这两种状态转换
- Pendding -> Resolved
- Pendding -> Rejected
既然是状态机,那么它肯定有输出,它的输出由其蕴含的异步过程给出,用 then 可以将其挖出来。
具体如何理解, 先定义如下的 Promise:
00let p = new Promise(function todo(resolve, reject){ 01 console.log('waitting'); 02 03 setTimeout(() => { 04 // 某些操作 05 resolve('ok'); 06 }, 1000); 07});
new Promise 将会返回一个 Promise 实例,里面包着一个异步过程。
再来看看构造这个实例的时候用的参数吧:
00function todo(resolve, reject){ 01 console.log('waitting'); 02 03 setTimeout(() => { 04 // 某些操作 05 resolve('ok'); 06 }, 1000); 07}
对于这个函数的参数的描述是:
resolve 是函数,一旦被执行,这个 Promise 就会变成 Resolved 态
reject 是函数,一旦被执行,这个 Promise 就会变成 Rejected 态
因此,是这样产生一个 Promise 实例的:
- 生成一个状态为 Pendding 的 Promise 实例
- 取出上述实例的状态转化器 resolve 和 reject
- 应用到 todo 函数
一开始的时候 Promise 是 Pendding 态的,约 1000 毫秒后,resolve 被执行,p 变成 Resolved 态了。
一旦 Promise 的实例完成了状态切换, 利用 then 方法就可以取得状态切换的时候被传递的参数 ( 这里是 ‘ok’ )
00p.then(ok => { 01 console.log(ok); 02 // => 03 // 'ok' 04});
此外,then 方法本身也会返回一个 Promise :
00var p2 = p.then(ok => { 01 console.log(ok); 02 // => 03 // 'ok' 04 05 return 'ok from p2'; 06}); 07 08p2.then(ok => { 09 console.log(ok); 10 // => 11 // 'ok from p2' 12}) 13 14p.then(ok => { 15 console.log(ok); 16 // => 17 // 依然是 'ok' 18})
这里的 p2 由 p 生成,蕴含的值是 ‘ok from p2’。
而且,应当看到,一旦状态确定 p 将永远不变,因此无论怎么搞,p 蕴含的值仍然还是 ‘ok’
而且既然 then 方法返回的是 Promise 那么,很自然的,可以链式的使用 then :
00let p3 = p.then(ok => { 01 console.log(ok); 02 // => 03 // 'ok' 04 return new Promise(resolve => { 05 resolve('ok from a new promise') 06 }) 07}).then(ok => { 08 console.log(ok); 09 // => 10 // 'ok from a new promise' 11});
注意 第四行 return 的值也可以是一个 Promise,之后的 then 将会如想象中的那样是上一次 Promise 的蕴含结果了。
# 函数式解释 ↵
Promise 的诸多行为都透露着函数式的气息,比如 then 本身会返回一个新 Promise,而不是从旧的 Promise 上改变状态而来,
此外,新的 Promise 还是旧 Promise 关于函数 F 的应用结果:
00let p1 = new Promise(res => { 01 res('hello, world ~ '); 02}); 03 04let F = str => str.toUpperCase(); 05 06let p2 = p1.then(F); 07// ps is promise of "HELLO, WORLD ~"
上面的代码无非这样:
p2 = F( p1 )
因而两个 Promise 存在映射关系,新的 Promise 是旧 Promise 关于 F 的一个应用。
故此,Promise 是一类 Mappable 的 Container,是一种 Functor,因而函数式的某些重要的信条也有所体现:
- 状态一经改变,则永远不变,并且总是产生新的 Promise,而不是改变系统的状态 (纯的)
- 副作用的操作,被容器包在 F 里了,观测性更强 (可控性更高)
# 可递归的一个实例 ↵
异步的过程,一般而言,是很难递归的处理的,但是如果有 Promise,则可以同步写异步地来写递归,好做的多。
现在有一个图片列表,如果想要一个接着一个的异步下载(不是一瞬间并发下载),递归实现如下:
00// dlOneByOne.js 01const rp = require('request-promise') 02 , fs = require('then-fs') 03 , url = require('url') 04 , path = require('path') 05 , STORE_TO = __dirname 06 , FILE_BASE = path.join(STORE_TO, 'd') 07 08function download(raw_url){ 09 const fileUrl = url.parse(raw_url) 10 , filePath = path.parse(fileUrl.path) 11 , fileName = filePath.base 12 , fileLocation = path.join(FILE_BASE, fileName) 13 , target = fs.createWriteStream(fileLocation) 14 15 console.log(fileLocation); 16 17 return new Promise((res, rej) => { 18 // Promise Resolved When Success 19 target.on('close', res); 20 // Promise Rejected When Error 21 target.on('error', rej); 22 // Start Downloading 23 rp.get(raw_url).pipe(target); 24 }); 25} 26 27var dlOneByOne = ([x, ...xs]) => ( 28 // x 存在吗? 29 x ? 30 // 存在 31 download(x).then(download_success => { 32 return dlOneByOne(xs); 33 }) : 34 // 不存在 35 Promise.resolve('All Done') 36); 37 38dlOneByOne.download = download 39 40module.exports = dlOneByOne;
按照如下方法使用:
00// test.js 01const dlOneByOne = require('./dlOneByOne.js') 02 03dlOneByOne([ 04 'http://example.com/test/a.jpg', 05 'http://example.com/test/b.png', 06 'http://example.com/test/c.jpg', 07 'http://example.com/test/d.png', 08]).then(ok => { 09 console.log('[ Succ ] All Done'); 10}).catch(err => { 11 console.log('[ Error ]', err); 12})
# Promise.all & Array.prototype.map ↵
字符串数组映射到 Promise 数组最后折合成一个 Promise
下面是一个并发下载全部文件的例子
00const { download } = require('./dlOneByOne.js'); 01 02Promise.all( 03 [ 04 'http://source.ijarvis.cn/blog/test/a.jpg', 05 'http://source.ijarvis.cn/blog/test/b.png', 06 'http://source.ijarvis.cn/blog/test/c.jpg', 07 'http://source.ijarvis.cn/blog/test/r.png', 08 'http://source.ijarvis.cn/blog/test/s.jpg', 09 'http://source.ijarvis.cn/blog/test/t.png', 10 'http://source.ijarvis.cn/blog/test/v.jpg', 11 'http://source.ijarvis.cn/blog/test/y.jpg', 12 'http://source.ijarvis.cn/blog/test/65912770_p2.png' 13 ].map(download) 14).then(all_dones => { 15 console.log('All Done'); 16});
# TL;DR ↵
就… 没有 Promise 根本没法编程 :p