正如同事所说的那样,Promise 在工做中表现优异。javascript
这篇文章会给你一些如何改善与 Promise 之间关系的建议。html
让我来讲明这最重要的一点java
是的!你能够在 .then 里面 return 一个 Promise
并且,return 的这个 Promise 将在下一个 .then
中自动解析。node
.then(r => { return serverStatusPromise(r); // 返回 { statusCode: 200 } 的 Promise }) .then(resp => { console.log(resp.statusCode); // 200; 注意自动解析的 promise })
若是熟悉 javascript 的链式风格,那么你应该会感到很熟悉。可是对于一个初学者来讲,可能就不会了。算法
在 Promise 中不论你使用 .then
或者 .catch
都会建立一个新的 Promise。这个 Promise 是刚刚链式调用的 Promise 和 刚刚加上的 .then
/ .catch
的组合。数组
让咱们来看一个 🌰:promise
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
。app
Promise
的 resolved/rejected
状态是惟一的我认为这个是让 Promise 好好运行的最重要的事情之一。简单来讲,若是在你的应用中 Promise 在不少不一样的模块之间共享,那么当 Promise 返回 resolved/rejected
状态时,全部的调用者都会收到通知。
这也意味着没有人能够改变你的 Promise,因此能够放心的把它传递出去。
function yourFunc() { const yourAwesomeProm = makeMeProm(); yourEvilUncle(yourAwesomeProm); // 不管 Promise 受到了怎样的影响,它最终都会成功执行 return yourAwesomeProm.then(r => importantProcessing(r)); } function yourEvilUncle(prom) { return prom.then(r => Promise.reject("destroy!!")); // 可能遭受的影响 }
经过上面的例子能够看出,Promise 的设计使得自身很难被改变。正如我上面所说的:"保持冷静,并将 Promise 传递下去"。
我看到不少开发者喜欢用构造函数的风格,他们认为这就是 Promise 的方式。但这倒是一个谎话,实际的缘由是构造函数 API 和以前回调函数的 API 类似,并且这样的习惯很难改变。
若是你发现本身正在处处使用 Promise 构造函数
,那你的作法是错的!
要真正的向前迈进一步而且摆脱回调,你须要当心谨慎而且最小程度地使用 Promise 构造函数。
让咱们看一下使用 Promise 构造函数
的具体状况:
return new Promise((res, rej) => { fs.readFile("/etc/passwd", function(err, data) { if (err) return rej(err); return res(data); }); });
Promise 构造函数
应该只在你想要把回调转换成 Promise 时使用。
一旦你掌握了这种建立 Promise 的优雅方式,它将会变的很是有吸引力。
让咱们看一下冗余的 Promise 构造函数
。
☠️错误的
return new Promise((res, rej) => { var fetchPromise = fetchSomeData(.....); fetchPromise .then(data => { res(data); // 错误!!! }) .catch(err => rej(err)) })
💖正确的
return fetchSomeData(...); // 正确的!
用 Promise 构造函数
封装 Promise 是多余的,而且违背了 Promise 自己的目的。
😎高级技巧
若是你是一个 nodejs 开发者,我建议你能够看一看 util.promisify。这个方法能够帮助你把 node 风格的回调转换为 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));
</div>
Javascript 提供了 Promise.resolve
方法,像下面的例子这样简洁:
var similarProm = new Promise(res => res(5)); // ^^ 等价于 var prom = Promise.resolve(5);
它有多种使用状况,我最喜欢的一种是能够把普通的(异步的)js 对象转化成 Promise。
// 将同步函数转换为异步函数 function foo() { return Promise.resolve(5); }
当不肯定它是一个 Promise 仍是一个普通的值的时候,你也能够作一个安全的封装。
function goodProm(maybePromise) { return Promise.resolve(maybePromise); } goodProm(5).then(console.log); // 5 var sixPromise = fetchMeNumber(6); goodProm(sixPromise).then(console.log); // 6 goodProm(Promise.resolve(Promise.resolve(5))).then(console.log); // 5, 注意,它会自动解析全部的 Promise!
Javascript 也提供了 Promise.reject
方法。像下面的例子这样简洁:
var rejProm = new Promise((res, reject) => reject(5)); rejProm.catch(e => console.log(e)) // 5
我最喜欢的用法是提早使用 Promise.reject
来拒绝。
function foo(myVal) { if (!mVal) { return Promise.reject(new Error('myVal is required')) } return new Promise((res, rej) => { // 从你的大回调到 Promise 的转换! }) }
简单来讲,使用 Promise.reject
能够拒绝任何你想要拒绝的 Promise。
在下面的例子中,我在 .then
里面使用:
.then(val => { if (val != 5) { return Promise.reject('Not Good'); } }) .catch(e => console.log(e)) // 这样是很差的
注意:你能够像 Promise.resolve
同样在 Promise.reject
中传递任何值。你常常在失败的 Promise 中发现 Error
的缘由是由于它主要就是用来抛出一个异步错误的。
Javascript 提供了 Promise.all 方法。像 ... 这样的简洁,好吧,我想不出来例子了😁。
在伪算法中,Promise.all
能够被归纳为:
接收一个 Promise 数组 而后同时运行他们 而后等到他们所有运行完成 而后 return 一个新的 Promise 数组 他们其中有一个失败或者 reject,均可以被捕获。
下面的例子展现了全部的 Promise 完成的状况:
var prom1 = Promise.resolve(5); var prom2 = fetchServerStatus(); // 返回 {statusCode: 200} 的 Promise Proimise.all([prom1, prom2]) .then([val1, val2] => { // 注意,这里被解析成一个数组 console.log(val1); // 5 console.log(val2.statusCode); // 200 })
下面的例子展现了当他们其中一个失败的状况:
var prom1 = Promise.reject(5); var prom2 = fetchServerStatus(); // 返回 {statusCode: 200} 的 Promise Proimise.all([prom1, prom2]) .then([val1, val2] => { console.log(val1); console.log(val2.statusCode); }) .catch(e => console.log(e)) // 5, 直接跳转到 .catch
注意:Promise.all
是很聪明的!若是其中一个 Promise 失败了,它不会等到全部的 Promise 完成,而是当即停止!
.catch
咱们是否是会常常担忧错误会在它们之间的某处被吞噬?
为了克服这个恐惧,这里有一个简单的小提示:
让 reject 来处理上游函数的问题。
在理想的状况下,reject 方法应该是应用的根源,全部的 reject 都会向下传递。
不要惧怕像下面这样写
return fetchSomeData(...);
如今若是你想要处理函数中 reject 的状况,请决定是解决问题仍是继续 reject。
💘 解决 reject
解决 reject 是很简单的,在 .catch
不论你返回什么内容,都将被假定为已解决的。然而,若是你在 .catch
中返回 Promise.reject
,那么这个 Promise 将会是失败的。
.then(() => 5.length) // <-- 这里会报错 .catch(e => { return 5; // <-- 从新使方法正常运行 }) .then(r => { console.log(r); // 5 }) .catch(e => { console.error(e); // 这个方法永远不会被调用 :) })
💔拒绝一个 reject
拒绝一个 reject 是简单的。不须要作任何事情。 就像我刚刚说的,让它成为其余函数的问题。一般状况下,父函数有比当前函数处理 reject 更好的方法。
须要记住的重要的一点是,一旦你写了 catch 方法,就意味着你正在处理这个错误。这个和同步 try/catch
的工做方式类似。
若是你确实想要拦截一个 reject:(我强烈建议不要这样作!)
.then(() => 5.length) // <-- 这里会报错 .catch(e => { errorLogger(e); // 作一些错误处理 return Promise.reject(e); // 拒绝它,是的,你能够这么作! }) .then(r => { console.log(r); // 这个 .then (或者任何后面的 .then) 将永远不会被调用,由于咱们在上面使用了 reject :) }) .catch(e => { console.error(e); //<-- 它变成了这个 catch 方法的问题 })
.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) { // 这个回调处理来自当前 `.then` 方法以前的错误 console.error(e); // 没有错误被打印出来 });
当你想要处理的是来自上游 Promise 而不是刚刚在 .then
里面加上去的错误的时候, .then(x,y)
变的很方便。
提示: 99.9% 的状况使用简单的 then(x).catch(x)
更好。
这个提示是相对简单的,尽可能避免 .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); } }) .then(r => r.text()) .catch(err => console.erro(err));
有些时候咱们在 .then
里面须要不少变量,那就别无选择了,只能再建立一个 .then
方法链。
.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return promA .then(valA => { return promB.then(valB => hungryFunc(valA, valB)); // 很丑陋! }) })
我推荐使用 ES6 的解构方法混合着 Promise.all
方法就能够解决这个问题。
.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return Promise.all([prom, anotherProm]) }) .then(([valA, valB]) => { // 很好的使用 ES6 解构 console.log(valA, valB) // 全部解析后的值 return hungryFunc(valA, valB) })
注意:若是你的 node/浏览器/老板/意识容许,还可使用 async/await 方法来解决这个问题。
我真心但愿这篇文章对你理解 Promise 有所帮助。