「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
作者: Ravidu Perera 译者:前端很美 来源:medium
前言
promise是js中处理异步操作的优雅方式,它是解决回调地狱的突破性解决方案,但是很多开发者因为没有真正理解它的原理,导致在实际使用promise的经常犯错。本文将例举5个常见的错误。
1.避免promise地狱
通常,Promise 用于避免回调地狱。 但是不正确的使用它们会导致 Promise 地狱。
userLogin('user').then(function(user){
getArticle(user).then(function(articles){
showArticle(articles).then(function(){
//Your code goes here...
});
});
});
在上面的例子中,我们嵌套了三个 Promise 到 userLogin、getArticle 和 showArticle。 如您所见,复杂性将与代码行成正比,并且可能变得不可读。 为了避免这种情况,我们需要通过从第一个 then 返回 getArticle的执行结果并在第二个 then 中处理它来避免嵌套代码。
userLogin('user')
.then(getArticle)
.then(showArticle)
.then(function(){
//Your code goes here...
});
2.在promise定义内部使用try/catch
通常,我们使用try/catch进行错误处理。 但是,不建议在 Promise对象中使用 try/catch。 这是因为如果有错误,Promise对象会在自身catch块内自动处理它们。
new Promise((resolve, reject) => {
try {
const data = doThis();
// do something
resolve();
} catch (e) {
reject(e);
}
})
.then(data => console.log(data))
.catch(error => console.log(error));
在上面的例子中,我们在 Promise内部使用了try/catch 块。但是promise可以在其定义内部不使用try/catch的情况下,捕捉到所有的异常。它可以捕捉执行期间发生的所有异常并且转化为rejected的promise。
new Promise((resolve, reject) => {
const data = doThis();
// do something
resolve()
})
.then(data => console.log(data))
.catch(error => console.log(error));
注意:在Promise链中使用.catch()至关重要。 否则,应用程序可能会在生产环境下崩溃。
3.在 Promise 块中使用异步函数
Async/Await 是一种更高级的语法,用于以同步代码的方式处理多个 Promise。当我们在函数声明之前使用 async 关键字时,它返回一个 Promise,我们可以使用 await关键字来暂停代码,直到我们等待的 Promise 发生了resolved 或者 reject.
但是将 Async 函数放入 Promise 块时,它会产生一些副作用。
假设你想在Promise 块中做一个异步操作,使用了 async 关键字,代码抛出了一个错误。
即使您使用 catch() 块或在 try/catch 块中await了Promise,也无法处理此错误。 请看示例:
// 以下两块代码都无法处理error
new Promise(async () => {
throw new Error('message');
}).catch(e => console.log(e.message));
(async () => {
try {
await new Promise(async () => {
throw new Error('message');
});
} catch (e) {
console.log(e.message);
}
})();
当在Promise块中遇到异步函数时,尝试将异步逻辑保留在 Promise 块之外,大部分情况可以解决问题,但是,在某些情况下,可能需要async函数。 在这种情况下,别无选择,只能使用 try/catch 块手动管理它。
//自己在promise块内使用try/catch来处理错误, 在catch里面reject
new Promise(async (resolve, reject) => {
try {
throw new Error('message');
} catch (error) {
reject(error);
}
}).catch(e => console.log(e.message));
//使用 async/await
(async () => {
try {
await new Promise(async (resolve, reject) => {
try {
throw new Error('message');
} catch (error) {
reject(error);
}
});
} catch (e) {
console.log(e.message);
}
})();
4.创建Promise后立即执行了Promise块
如下代码,它会立即执行http 请求
const myPromise = new Promise(resolve => {
// HTTP 请求的代码
resolve(result);
});
原因是因为http请求代码在Promise构造函数内,然而开发中真正的需求可能是希望在then方法后面的代码去执行http请求,然而,当一个 Promise 被创建时,构造函数里的回调代码会立即执行。你的http请求可能已经在运行了,但是,如果您想稍后执行 Promise,您应该怎么做? 如果你现在不想发出HTTP 请求怎么办? Promise 中是否有任何神奇的机制可以让您做到这一点?答案比想象的要简单。 函数是一种延时机制。只有当开发人员用()明确调用它们时,它们才会执行。因此,使Promise变得懒惰的最有效方法是将其包装在一个函数中!
const createMyPromise = () => new Promise(resolve => {
// HTTP request
resolve(result);
});
Promise构造函数包裹在一个函数内,这种模式下还没有调用任何东西。 所以我们修改了变量名,因为它不再是一个Promise 而是一个创建并返回Promise对象的函数。 对于 HTTP 请求,Promise 构造函数和里面的回调函数只会在函数执行时被调用。 所以现在我们有一个懒惰的 Promise,它只在我们需要的时候执行。
5.没有及时使用Promise.all()方法
如果您有许多彼此不相关的Promise,您可以同时resolve它们。Promises 是并发的,但如果你一次等待一个,它会花费太长时间。 使用 Promise.all() 可以节省很多时间。
const { promisify } = require('util');
const sleep = promisify(setTimeout);
async function f1() {
await sleep(1000);
}
async function f2() {
await sleep(2000);
}
async function f3() {
await sleep(3000);
}
(async () => {
console.time('sequential');
await f1();
await f2();
await f3();
console.timeEnd('sequential');
})();
执行上面的代码大约需要 6 秒。 但是如果我们用 Promise.all() 替换它,它将减少执行时间。
(async () => {
console.time('concurrent');
await Promise.all([f1(), f2(), f3()]);
console.timeEnd('concurrent');
})();
总结
本文讨论了开发人员在 js 中使用 Promise 时常犯的五个错误,可能还有一些别的问题需要注意。