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