ECMAScript6发布到如今差很少有5年时间了。在这5年时间里ES6摧枯拉朽般的将现代前端“改朝换代”,Promise是其中“大将”般的存在,影响着无数的前端库和API。能够这么说,Promise已是现代前端的“血液”。html
尽管通过5年的日日夜夜,尽管书写过数不尽的Promise。面对着这个时而让咱们感到真棒,用的舒服、时而坑得咱们踉踉跄跄的API,咱们真的了解它吗?前端
相信许多开发者最开始对Promise感到陌生的情景就是:不知道怎么跟循环结合使用。 例如:git
// 我想将数组下的每一个元素都执行一个函数
fetchSomeData().then((res) => {
res.data.forEach((item) => {
doSomethingFunction(item);
})
}).then(res => {
// 作其余事
})
复制代码
这个例子有什么问题呢?github
问题在于:第一个then回调函数返回的是undefined,就是说第二个then函数并无等doSomethingFunction(item);执行完。事实上,它并不须要等待任何事情,而且能够在doSomethingFunction(item);执行了几个后执行。数组
这是一个很是隐蔽的错误,由于若是res.data足够小或者doSomethingFunction()执行的足够快,可能就不会发现任何问题。promise
如何解决?须要用到Promise.all()。缓存
fetchSomeData().then(res) => {
return Promise.all(res.data.map(item) => {
return doSomethingFunction(item);
})
}).then(res => {
// 作其余事
})
复制代码
Promise.all接收一个Promise对象组成的数组做为参数,当这个数组全部的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。bash
fetchSomeData().then((res) => {
doSomethingFunction(res);
}).then(res => {
// 作其余事
})
复制代码
这个例子的问题在于第二个then函数获取的是undefined。使用了side effect去改变而不是返回。异步
每个Promise都有一个then方法,咱们能在then方法中作三件事情:ide
fetchSomeData().then((res) => {
return getId(res);
}).then(res => {
// 我能获得id
})
复制代码
使用return 返回第二个Promise,在第二个then方法中就能获得id。若是没有return,那么getId()只是一个side effect,那么第二个then方法只能获得undefined。
好比说要对id作一个缓存处理,以下降运行时间。
fetchSomeData().then((res) => {
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能获得id
})
复制代码
无论id是缓存中的,仍是异步去获取的,都能返回正确的。
throw error能让Promise变得更严谨。若是要在用户登出的时候作错误处理:
fetchSomeData().then((res) => {
if (logout) {
throw new Error('用户已登出');
}
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能获得id
}).catch(err=> {
// 作错误处理
})
复制代码
catch方法能获取获得错误。
若是常常写出下面内容:
new Promise((resolve, reject) => {
resolve(doSomething())
}).then(...)
复制代码
其实就是对Promise不熟悉,能够用更简短的语句去表达
Promise.resolve(doSomething()).then(...)
复制代码
一样Promise.reject()能够返回当即被拒绝的Promise
Promise.reject(new Error('some error'))
复制代码
其实catch方法是then(null, function(err) {})的语法糖
下面这两段代码是相等的
promise().catch(err => {
// 处理错误
})
promise().then(null, err => {
// 处理错误
})
复制代码
但并不意味着下面这两段代码是相等的
promise().then((res) => {
return otherPromise(res);
}).cathc(err => {
// 能捕得到到错误
})
promise().then(res => {
return otherPromise(res);
}, err => {
// 不能捕得到到错误
})
复制代码
因此,当使用then(resolveHandler, rejectHandler)时,若是它自己发生错误,rejectHandler是不会捕得到到的。
出于这个缘由,捕获错误尽可能使用catch方法。
若是要执行一系列的promise,相似Promise.all()方法,但不会并行执行。可能会写出下面的代码
function execute(promises) {
var result = Promise.resolve();
promise.forEach(promise => {
result = result.then(promise);
});
return result;
}
复制代码
不幸的是,这没法按照预期去执行,仍然是并行执行的。
发生这种状况的缘由是:预期是不但愿对一系列的promise进行操做。可是根据promise规范,一旦建立了promise,它就会开始执行。
所以要用到promise工厂函数
function execute(promiseFactories) {
var result = Promise.reslove();
promiseFactories.forEach(promiseFactory => {
result = result.then(promiseFactory);
});
return result;
}
复制代码
promise工厂函数很是简单,只是一个返回promise的函数
function promiseFactory() {
return promiseCreated();
}
复制代码
这种方法之因此会有效,是由于promise工厂函数直到被调用时才建立promise。它与then函数的工做方式相同
你认为下面代码的输出是什么?
Promise.resolve('foo').then(Promise.resolve('bar')).then((res) => {
console.log(res);
})
复制代码
若是你认为输出bar,那就错了。实际上输出的是foo!
由于当传递给then()方法并不是是一个函数时,它实际上执行then(null),这样先前的promise结果就没法传给第二个then方法。
Promise.resolve('foo').then(null).then(res => {
console.log(res) // foo
})
复制代码
简而言之,能够将promise直接传给then方法,但它并不会按照你的预期去执行。因此你要这样作
Promise.resolve('foo').then(() => {
return Promise.resolve('bar')
}).then(res => {
console.log(res); // bar
})
复制代码
所以,请提醒本身:始终要将函数传递给then方法
有人说:一回生二回熟。
经历了上述这六回,相信对promise就像亲人通常的熟悉。
上述文章是翻译、加工自We have a problem with promises
更多文章请移步楼主github,若是喜欢请点一下star,对做者也是一种鼓励。