工程师们, 你们总说 Pormise 好用! 但有时候用起来是不是还很懵逼本文传授给你九条实用的 Promise 使用技巧, 帮助你和它建立起良好的关系!
1. 你可以在 .then 回调里返回 Promise
我必须大声喊出来:
是的! 你可以 .then 回调里返回 Promise!
而且, 返回的 promise 会在接下来的 .then 被自动打开 (unwrapped):
- .then(r => {
- // 这是一个 { statusCode: 200 } promise
- return serverStatusPromise(r);
- })
- .then(resp => {
- // 200; 注意上面的 promise 被自动 unwrap 了
- console.log(resp.statusCode);
- })
2. 每次调用 .then 都会产生一个新的 Promise
如果你熟悉 JavaScript 的链式调用, 对这种用法一定不陌生
调用 .then 和 .catch 时都会创建一个新的 Promise 这个新的 Promise 可以继续使用 .then 或者 .catch
- var
- statusProm = fetchServerStatus
- ();
- var
- promA = statusProm
- .then(
- r =>
- (r.
- statusCode ===
- 200 ?
- "good"
- : "bad"
- ));
- var
- promB = promA
- .then(
- r =>
- (r ===
- "good"
- ? "ALL OK"
- :
- "NOTOK"));
- var
- promC = statusProm
- .then(
- r => fetchThisAnotherThing
- ());
上面出现的 promise 可以用下面这个流程图来表示:
重点注意, promA promB 和 promC 虽然相关, 但都是不同的 promise 实例
我喜欢把这种 .then 链当做一个巨大的水暖系统, 如果父节点出了故障, 热水将无法流入到自节点中例如, 如果 promB 故障了, 其他节点不受影响, 但是如果 statusProm 出问题了, 其他节点就会被影响, 也就是被 rejected
3. 在任何情况下, Promise resolve/reject 状态都是一致的
这是 Promise 之所以好用的原因简单理解, 就是如果一个 promise 在多个地方使用, 当它被 resolve 或者 reject 的时候, 都会获得通知
而且 promise 是无法被修改的, 因此它可以随意传递
- function yourFunc() {
- const yourAwesomeProm = makeMeProm();
- // 无论坏叔叔如何消费你的 promise, 你的 promise 都可以正常工作
- yourEvilUncle(yourAwesomeProm);
- return yourAwesomeProm.then(r => importantProcessing(r));
- }
- function yourEvilUncle(prom) {
- // 坏叔叔
- return prom.then(r => Promise.reject("destroy!!"));
- }
Promise 的设计避免了恶意的破坏, 如我所说: 没事, 可以把 promise 随便扔!
4. Promise 构造函数不是万金油
我发现有些工程师在任何地方都会使用 Promise 的 constructor, 还认为这就是 promise 的使用方式这是不对的, 根本原因就是 constructor API 与原来 callback API 很像, 老的习惯很难改
如果你的代码中遍布 Promise constructor, 你的做法就是错的!
如果你想更进一步, 摆脱回调函数, 你应该尽量减少 Promise 构造函数的使用
Promise 构造函数正确的使用场景如下:
- return new Promise((res, rej) => {
- fs.readFile("/etc/passwd", function(err, data) {
- if (err) return rej(err);
- return res(data);
- });
- });
Promiseconstructor 只在将回调转成 promise 时使用
看一个冗余的例子:
- // 错误用法
- return new Promise((res, rej) => {
- var fetchPromise = fetchSomeData(.....);
- fetchPromise
- .then(data => {
- res(data); // 错误的方式
- })
- .catch(err => rej(err))
- })
- // 正确用法
- // 看上去对就是对的
- return fetchSomeData(...);
在 Node.js 中, 推荐使用 util.promisify 用来将回调 API 转成 promise 式的:
- const {promisify} = require('util');
- const fs = require('fs');
- const readFileAsync = promisify(fs.readFile);
- readFileAsync('myfile.txt', 'utf-8')
- .then(r => console.log(r))
- .catch(e => console.error(e));
5. 使用 Promise.resolve
JavaScript 提供了 Promise.resolve API, 是产生 Promise 对象的一种快捷方式, 这个 promise 对象是被 resolve 的
- var similarProm = new Promise(res => res(5));
- // 相当于
- var prom = Promise.resolve(5);
这有很多使用场景, 我最喜欢的一个是, 将一个同步的对象转成一个 promise:
- // 将一个同步函数转成异步的
- function foo() {
- return Promise.resolve(5);
- }
也可以用来在不确定返回值是普通对象还是 promise 时, 将返回值封装为 promise 对象:
- function goodProm(maybePromise) {
- return Promise.resolve(maybePromise);
- }
- goodProm(5).then(console.log); // 5
- // 这个 promise resolve 成 5
- var sixPromise = fetchMeNumber(6);
- goodProm(sixPromise).then(console.log); // 6
- // 5, 注意, 每层 promise 都被自动 unwrap 了
- goodProm(Promise.resolve(Promise.resolve(5))).then(console.log);
6. 使用 Promise.reject
与 Promise.resolve 类似, 它也是一种快捷写法
- var rejProm = new Promise((res, reject) => reject(5));
- rejProm.catch(e => console.log(e)) // 5
我最喜欢的 Promise.reject 的用法是, 尽早地 reject:
- function foo(myVal) {
- if (!mVal) {
- return Promise.reject(new Error('myVal is required'))
- }
- return new Promise((res, rej) => {
- // 这些将巨大的 callback 转成 promise
- })
- }
在 .then 中使用 reject:
- .then(val => {
- if (val != 5) {
- return Promise.reject('Not Good');
- }
- })
- .catch(e => console.log(e)) // Not Good
7. 使用 Promise.all
JavaScript 还提供了 Promise.all, 但它不是什么快捷方式
可以如下总结它的算法:
接受一个 promise 的数组
等待所有这些 promise 完成
返回一个新的 Promise, 将所有的 resolve 结果放进一个数组里
只要有一个 promise 失败 / rejected, 这个新的 promise 将会被 rejected
下例展示了所有 promise 都 resolve 的情况:
- var prom1 = Promise.resolve(5);
- var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}
- Proimise.all([prom1, prom2])
- .then([val1, val2] => { // notice that it resolves into an Array
- console.log(val1); // 5
- console.log(val2.statusCode); // 200
- })
下例展示有一个失败的情况:
- var prom1 = Promise.reject(5);
- var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}
- Proimise.all([prom1, prom2])
- .then([val1, val2] => {
- console.log(val1);
- console.log(val2.statusCode);
- })
- .catch(e => console.log(e)) // 5, jumps directly to .catch
注意: Promise.all 是一点不笨, 只要有一个 promise 被 reject 了, 它就直接 reject, 不会等到其他 promise 完成
8. 不要害怕 reject 或者不要在每一个 .then 后面使用
- .
- catch
我们是不是常常感到有很多隐藏的错误没有被处理?
不用担心像下面这样写:
return fetchSomeData(...);
你可以在任何你想处理的地方解决或者延续 rejection
处理掉 rejection
这很简单, 在 .catch 回调中, 无论你返回什么都会变成 resolve, 除非你返回一个 Promise.reject, 才会延续 rejection
- .then(() => 5.length) // <-- something wrong happenned here
- .catch(e => {
- return 5; // <-- making javascript great again
- })
- .then(r => {
- console.log(r); // 5
- })
- .catch(e => {
- console.error(e); // this function will never be called :)
- })
- reject rejection
reject rejection 的方法就是什么都不做通常, 父函数比起当前函数更擅长处理 rejection
要记住一个要点, 一旦你写了 .catch 就意味着 rejection 已经被处理了, 这与同步的 try/catch 类似
如果你确实想要阻断 rejection(我强烈不推荐这么做):
- .then(() => 5.length) // <-- something wrong happenned here
- .catch(e => {
- errorLogger(e); // do something impure
- return Promise.reject(e); // reject it, Yes you can do that!
- })
- .then(r => {
- console.log(r); // this .then (or any subsequent .then) will never be called as we rejected it above :)
- })
- .catch(e => {
- console.error(e); //<-- it becomes this catch's problem
- })
.then(x,y) 和
- then
- (x
- ).catch
- (x
- )
.then 接受第二个回调参数来处理错误虽然与 then(x).catch(x) 看一起类似, 但却有所不同, 不同点在于可捕获的错误
下面例子很好地说了这个问题:
- .then(function() {
- return Promise.reject(new Error('something wrong happened'));
- }).catch(function(e) {
- console.error(e); // something wrong happened
- });
- .then(function() {
- return Promise.reject(new Error('something wrong happened'));
- }, function(e) { // callback handles error coming from the chain above the current `.then`
- console.error(e); // no error logged
- });
9. 避免 .then 嵌套
这个原则理解起来很简单, 就是避免在 .then 里面继续使用 .then 或者 .catch 相信我, 这绝对是可以避免的
- // 错误用法
- request
- (opts)
- .
- catch(err
- => {
- if (
- err.statusCode
- === 400
- ) {
- return request(
- opts)
- .then(
- r => r
- .text())
- .catch(
- err2 => console
- .error(
- err2))
- }
- })
- // 正确用法
- request
- (opts)
- .
- catch(err
- => {
- if (
- err.statusCode
- === 400
- ) {
- return request(
- opts);
- }
- return Promise
- .reject(
- err);
- })
- .
- then(r
- => r.
- text())
- .
- catch(err
- => console.erro
- (err));
就算是如下这种情况, 也可以使用 Promise.all 来解决:
- .then(myVal => {
- const promA = foo(myVal);
- const promB = anotherPromMake(myVal);
- return promA
- .then(valA => {
- return promB.then(valB => hungryFunc(valA, valB)); // very hungry!
- })
- })
可以像下面这样:
- .then(myVal => {
- const promA = foo(myVal);
- const promB = anotherPromMake(myVal);
- return Promise.all([prom, anotherProm])
- })
- .then(([valA, valB]) => { // putting ES6 destructing to good use
- console.log(valA, valB) // all the resolved values
- return hungryFunc(valA, valB)
- })
好了, 真心希望本文可以帮到你更好地理解 Promise!
来源: https://juejin.im/entry/5a9d77c651882555691876d7