在JavaScript语言中,代码都是是单线程执行的,正是因为这个缘由,致使了JavaScript中全部的网络操做,浏览器事件,都必须知足异步执行的要求。因此异步的各类方案开始出现并逐步合理化,简单话!api
在开发过程当中你们使用的异步处理方案通常包括:回调函数(Callback)
、Promise
、Generator
函数、async/await
。这里就主要说一下这些方案的异同:数组
假设咱们定义一个getData
函数用于数据请求:promise
function getData(url, callback) {
// 模拟数据请求
setTimeout(() => {
let res = {
url: url,
data: {}
}
callback(res)
}, 1000)
}
复制代码
如今的需求是咱们须要依次请求三次服务器,而且每次请求的数据必须在上次成功的基础上执行:浏览器
getData('/api/page/1?params=123',(res1) => {
console.log(res1);
getData(`/api/page/2?params=${res1.data.params}`, (res2) => {
console.log(res2);
getData(`/api/page/3?params=${res2.data.params}`, (res3) => {
console.log(res3);
})
})
})
复制代码
经过上面的🌰,咱们能够看到第一次的url:/api/page/1?params=123
,第二次的url: /api/page/2?params=${res1.data.params}
,依赖第一次请求的数据,第三次的url:/api/page/2?params=${res2.data.params}
,依赖第二次请求的数据。因为咱们每次的数据请求都依赖上次的请求,因此咱们将会将下一次的数据请求以回调函数的形式写在函数内部,这其实就是咱们常说的回掉地狱
!服务器
一样的需求,咱们使用Promise
,去实现看看:网络
首先咱们须要先将咱们的getData
函数改写成Promise
的形式异步
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
复制代码
那么逻辑代码应该变成:async
getDataPromise('/api/page/1?params=123')
.then(res1 => {
console.log(res1);
return getDataPromise(`/api/page/2?params=${res1.data.params}`)
})
.then(res2 => {
console.log(res2);
return getDataPromise(`/api/page/3?params=${res2.data.params}`)
})
.then(res3 => {
console.log(res3);
})
复制代码
这样写完来看,发现咱们每次在数据请求成功(then
)以后返回一个Promise
对象,方便下次使用,这样咱们就避免了回掉地狱
的出现,可是这样其实也不算事完美,当咱们的请求变得复杂的时候咱们会发现咱们的代码会变的更加复杂。函数
为了不这种状况的出现 async/await
应运而生。ui
getData
函数不变,仍是Promise
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
复制代码
需求代码变成:
async function getData () {
let res1 = await getDataPromise('/api/page/1?params=123');
console.log(res1);
let res2 = await getDataPromise(`/api/page/2?params=${res1.data.params}`);
console.log(res2);
let res3 = await getDataPromise(`/api/page/2?params=${res2.data.params}`);
console.log(res3);
}
复制代码
怎么样,是否是这段代码阅读起来很是舒服,其实async/await
都是基于Promise
的,使用async
方法最后返回的仍是一个Promise
;实际上async/await
能够看做是Generator
异步处理的语法糖,👇咱们就来看一下使用Generator
怎么实现这段代码
// 异步函数依旧是Promise
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
function * getData() {
let res1 = yield getDataPromise('/api/page/1?params=123');
console.log(res1);
let res2 = yield getDataPromise(`/api/page/2?params=${res1.data.params}`);
console.log(res2);
let res3 = yield getDataPromise(`/api/page/2?params=${res2.data.params}`);
console.log(res3);
}
复制代码
其实能够分开来看:
let fn = getData()
fn.next().value.then(res1 => {
fn.next(res1).value.then(res2 => {
fn.next(res2).value.then( () => {
fn.next()
})
})
})
复制代码
上面的代码咱们能够看到,next()
每一步之行.value
方法返回的都是一个Promise
,因此咱们能够在后面添加then
方法,在then
方法后面我继续调用next()
,知道函数运行完成。实际上上面的代码咱们不须要手动去写,咱们能够对其封装一下:
function run(gen) {
let fn = gen()
function next(data) {
let res = fn.next(data)
if (res.done) return res.value
res.value.then((info) => {
next(info)
})
}
next()
}
run(getData)
复制代码
run
方法用来自动执行一步操做,其实就能够看做是Generator
在进行递归
操做;
这样咱们就将异步操做封装到了函数内部,其实不难发现async/await
和Generator
有不少类似的地方,只不过async/await
在语义上更容易被理解。
在使用async/await
的时候咱们不须要在去定义run()
,它内部已经给咱们定义封装好了,这也是为何说async/await
是Generator
异步处理的语法糖了。
上面咱们介绍了回调函数(Callback)
、Promise
、Generator
函数、async/await
的区别,下面咱们就来具体说说Promise
。
then
和 Promise.prototype.catch()
方法都会返回 promise
,它们能够被链式调用 — 一种称为复合composition
的操做.
第一个参数:状态从 pending
-> fulfilled
时的回调函数
第二个参数:状态从 pending
-> rejected
时的回调函数
返回值:新的 Promise
实例(注意不是原来的 Promise
实例)
特色
因为 then
方法返回一个新的 Promise
实例,因此 then
方法是能够链式调用的,链式调用的 then
方法有两个特色:
第一:后一个 then
方法的回调函数的参数是前一个 then
方法的返回值
第二:若是前一个 then
方法的返回值是一个 Promise
实例,那么后一个 then
方法的回调函数会等待该 Promise
实例的状态改变后再执行
catch 方法能够用于您的promise组合中的错误处理。
Internally calls Promise.prototype.then on the object upon which is called, passing the parameters undefined and the onRejected handler received; then returns the value of that call (which is a Promise).
你们能够看一下下面的代码:
const promise = new Promise(function (resolve, reject) {
setTimeout(() => {
reject('err')
}, 1000)
})
promise.then(
res => console.log('s1'),
err => console.log('e1')
).then(
res => console.log('s2')
).catch(
err => console.log('e2')
)
复制代码
e1
s2
复制代码
能够发现,在第一个 then
方法执行的错误处理函数中捕获到了错误,因此输出了 e1
,那么这个错误已经被捕获到了,也就不须要 catch
再次捕获了,因此没有输出 e2
,这是正常的,但问题是居然输出了 s2
。。。。 因此为了不这种状况代码应该改成:
promise.then(
res => console.log('s1')
).then(
res => console.log('s2')
).catch(
err => console.log('e2')
)
复制代码
这样只会输出e2
了
当咱们想在Promise
不管成功仍是失败的时候都想进行某一步操做时,能够说使用finally
promise.then(
res => console.log('s1')
).catch(
err => console.log('e1')
).finally(
() => console.log('end')
)
复制代码
很容易可以发现,.finally
只不过是一个成功与失败的回调函数相同的 .then
而已。
参数(iterable) 一个可迭代的对象,如 Array 或 String;
返回值
🌰:
const p = Promise.all([promise1, promise2, promise3])
p.then(
(res) => {
// res 是结果数组
}
)
复制代码
只有当全部
Promise
实例的状态都变为fulfilled
,那么Promise.all
生成的实例才会fulfilled
。 只要有一个Promise
实例的状态变成rejected
,那么Promise.all
生成的实例就会rejected
。
做用:与 Promise.all
相似,也是将多个 Promise
实例包装成一个 Promise
实例。
参数:与 Promise.all
相同
特色:
Promise.race
方法生成的 Promise
实例的状态取决于其所包装的全部 Promise
实例中状态最早改变的那个 Promise
实例的状态。
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它能够是完成( resolves),也能够是失败(rejects),这要取决于第一个完成的方式是两个中的哪一个。 若是传的迭代是空的,则返回的 promise 将永远等待。 若是迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
const promise = Promise.race([
getData('/path/data'),
new Promise((resolve, reject) => {
setTimeout(() => { reject('timeout') }, 10000)
})
])
promise.then(res => console.log(res))
promise.catch(msg => console.log(msg))
复制代码
做用:将现有对象(或者原始值)转为 Promise
对象。
参数:参数能够是任意类型,不一样的参数其行为不一样
Promise
对象,则原封不动返回thenable
对象(即带有 then
方法的对象),则 Promise.resolve
会将其转为 Promise
对象并当即执行 then
方法Promise.resolve
会将其包装成 Promise
对象,状态为 fulfilled
fulfilled
的 Promise
对象Promise.reject(reason)
方法返回一个带有拒绝缘由reason参数的Promise对象。
通常经过使用Error的实例获取错误缘由reason对调试和选择性错误捕捉颇有帮助。
Promise.reject('err')
// 等价于
new Promise(function (resolve, reject) {
reject('err')
})
复制代码
其实咱们在js中能够将同步代码也可以使用Promise
function a() {
console.log('aaa')
}
// 等价于
const p = new Promise((resolve, rejext) => {
resolve(a())
})
复制代码
或者点击Promise