Promises

约定

不同于之前的回调函数,在使用Promise时会有以下约定:

  1. 在本轮事件循环运行完成之前,回调函数是不会调用的
  2. 即使异步操作操作已经完成(成功或失败),在这时候通过then()添加的回调函数也会被调用
  3. 通过多次then()可以添加多个回调函数,他们会按照插入顺序执行

Promise支持链式调用。

链式调用

通过创造一个Promise链来实现连续执行两个或者多个异步操作,在上一个操作执行成功之后,待着上一个操作返回的结果开始下一个的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
P// then()函数会返回一个和原来不同的新的Promise
const promise = doSomething();
const promise2 = peomise.then(successCallBack, failureCallback); // promise2不仅代表着doSomething函数的完成,也代表了成功或者失败的回调函数的完成。每个promise都代表了链中另一个异步过程的完成

// 回调地域
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(thirdResult) {
console.log(thirdResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);


// Promise链式调用
doSomething().then(function(result) {
return doSomethingElse(result); // 一定要有返回值callback才能获取上一个Promise的结果
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(ThirdReult) {
console.log(ThirdReult);
})
.catch(failureCallback); // then里的参数是可选的 .catch(failureCallback)是.then(null, failureCallback)的缩略形式。
catch的后续链式操作

有可能会在一个回调失败之后继续使用链式操作,即使用一个catch()。这对于在链式操作中抛出一个错误之后再次进行新的操作会很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Promise((resolve, reject) => {
console.log('init...');
resolve();
})
.then(() => {
throw new Error('出错了');
console.log('出错之后发生的'); // 永远不会执行
})
.catch(() => {
console.log('捕获到错误了');
})
.then(() => {
console.log('无论有没有错都会执行');
})

错误传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 在Promise链中错误回调只有尾部的一次调用
// 一遇到异常抛出,浏览器就会顺着Promise链寻找下一个onRejected失败回调函数或者由catch指定的回调函数
doSomething()
.then(result => doSometingElse(result));
.then(newResult => doThirdThing(newResult));
.then(ThirdResult => console.log(ThirdResult));
.catch(failureCallback);

// 执行过程类似于下面的同步代码
try {
let result = syncDoSomething();
let newResult = syncDoSomrthingElse(result);
let ThirdResult = syncDoThirdThing(newResult);
cosole.log(ThirdResult);
} catch(error) {
failureCallback(error);
}

// 在ECMAScript2017中,可以使用async/await语法糖
async function() {
try {
let result = await doSomething();
let newResult = await doSomrthingElse(result);
let ThirdResult = await doThirdThing(newResult);
cosole.log(ThirdResult);
}catch(error) {
failureCallback(error);
}
}

Promise拒绝事件

当Promise被拒绝时,会将rejectionhandled(提供reject函数)或unhandledrejection(没有提供reject函数)派发到全局作用域(通常是window)。

PromiseRejectionEvent事件通常有两个属性:promiseressonpromise属性指向被驳回的Promise,reason属性用来说明Promise被驳回的原因。在每一个上下文中,该处理都是全局的,所有的错误都会在同一个handle中被捕捉处理。

1
2
3
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
}, false)

在旧式回调API中创建Promise

可以通过Promise的构造器从零开始创建Promise。该方式应当只在包裹旧API的时候用到。理想状态下,所有的异步函数都已经返回Promise了。但一些API仍使用旧方式来传入成功或失败的回调:

1
setTimeout(() => doSomething(), 10000);

混用旧式回调和Promise可能会造成运行时序的问题,如果doSomething函数抛出异常,那就没办法捕获它了。

可以使用Promise来包裹它:

1
2
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(10000).then(() => doSomething()).catch(failureCallback);

通常Promise的构造器只接受一个执行函数,可以在这个函数里手动的resolve和reject一个Promise。

组合

Promise.resolve()Promise.reject()是手动创建一个已经resolve或者reject的Promise快捷方法。Promise.all()Promise.race()是并行运行异步操作的两个组合式工具。可以发起并行操作,然后等多个操作全部结束后进行下一个操作:

1
Promise.all([func1(), func2()]).then(([res1, res2]) => {console.log(res1, res2)});

也可以使用一些JavaScript写法实现时序组合:

1
[func1(), func2(), func3()].reduce((p, f) => p.then(f), Promise.resolve()).then(res3 => console.log(res3));

通常递归调用一个由异步函数组成的数组时相当于一个Promise链:

1
Promise.resolve().then(func1).then(func2).then(func3);

也可以写成可复用的函数形式:

1
2
const applyAsync = (acc, val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

composeAsync函数接受任意数量的函数作为参数,并返回一个新的函数,该函数接收一个通过composition pipeline传入的初始值,它们能确保按顺序执行:

1
2
const transformData = composeAsync(fun1, fun2, fun3);
const result3 = transformData(data);

在ECMAScript2017中,时序组合可以使用async/await

1
2
3
4
let result;
for(const f of [func1, func2, func3]) {
result = await f(result);
}

时序

为了避免意外,即使是一个已经变成resolve状态的Promise。传递给then函数也总会被异步调用:

1
2
Promise.resolve().then(() => console.log(1));
console.log(2); // 2, 1

传递给then中的函数被置入了一个微任务队列,而不是立即执行,这意味着它是在JavaScript事件队列的所有运行时结束了事件队列被清空之后才会执行:

1
2
3
4
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

嵌套

嵌套Promise是一种可以限制catch语句作用域的控制结构。准确来说,嵌套的catch仅捕获在其之前同时还必须是其作用域的异常,而不捕获嵌套之外的或者在其链式以外的异常:

1
2
3
4
5
6
7
doSomething()
.then(result => doSomething1()
.then(result1 => console.log(result1))
.catch(err => console.log(err))
)
.then(() => doSomething2())
.catch(e => console.log(e.message)); // 不会输出

本文参考资料

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • © 2019-2021 musi

请我喝杯咖啡吧~

支付宝
微信