全部 / 前端 / 技术 · 2022年6月12日 0

35 – Promises:链式、错误处理和运算符

广告位招租 (vx: ghostcode, 备注:网站广告)

原文:https://dev.to/bhagatparwinder/promises-chaining-error-handling-operators-3ccb

上篇文章详细的介绍了什么是 promise 以及如何创建、 resolve 和 reject。

这一次,我们将讨论 promise 中的链式操作以及错误处理和可用的运算符。

链式

回调函数最显著的缺点之一是当我们连接它们时形成的嵌套结构,在 then 的帮助下,我们可以创建一个更易阅读、理解和调试的扁平结构。

假设我们有一个 waitForMe 的函数返回 promise,这个函数等待 2 秒后会返回你朋友的名字。

const waitForMe = function(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            return resolve(name);
        }, 2000);
    });
}

waitForMe("Parwinder")
    .then((data) => {
        console.log(data); // Outputs/yells "Parwinder" after 2 second
    });

假设你有很多懒惰的朋友,因为你很着急想都给他们打电话。我们可以一个个的给他们打电话(链式操作)。

waitForMe("Parwinder")
    .then((data) => {
        console.log(data); // waits 2 seconds and outputs "Parwinder"
        return waitForMe("Lauren");
    })
    .then((data) => {
        console.log(data); // waits another 2 seconds and outputs "Lauren"
        return waitForMe("Robert");
    })
    .then((data) => {
        console.log(data); // waits another 2 seconds and outputs "Robert"
        return waitForMe("Eliu");
    })
    .then((data) => {
        console.log(data); // waits another 2 seconds and outputs "Eliu"
    })

你会看到我们是如何用链式调用名字的以及控制台间隔 2 秒打印出它们,每一个 then 操作符会返回一个 promise 然后和其他的 then 链起来,同时保持代码结构的扁平。

错误处理

在 promise 的链式中有两种方法可以处理错误,要么在 then 块中传入错误处理器或者使用 catch 操作符。我们已经在前一篇文章中讨论了第一种方法。

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("an error has occurred");
    }, 2000)
});

myPromise.then((response) => {
    console.log(response);
}, (error) => {
    console.log(error); // an error has occurred
});

在上面的例子中,then 包含两个回调,第一个是成功的处理器,第二个是错误处理器。使用这两个处理器是完全没有问题的同时在多数情况下工作良好。它也有某些缺点:

  1. 如果成功处理器中产生了错误,你将无法捕获或处理它;
  2. 如果你像上面的链式例子一样使用链式调用,你需要在每个 then 块中添加错误处理器。

为了解决这些缺点,我们使用 catch 操作符。

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("an error has occurred");
    }, 2000)
});

myPromise.then((response) => {
    console.log(response);
}).catch((error) => {
    console.log(error); // an error has occured
});

在 promise 的链式调用中,我们可以这样使用 catch 操作符:

const waitForMe = function (name) {
    return new Promise((resolve, reject) => {
        if (name === "Robert") {
            return reject("Robert is always on time");
        } else {
            setTimeout(() => {
                return resolve(name);
            }, 2000);
        }
    });
}

waitForMe("Parwinder")
    .then((data) => {
        console.log(data); // wait 2 second and log "Parwinder"
        return waitForMe("Lauren");
    })
    .then((data) => {
        console.log(data); // wait 2 more seconds and log "Lauren"
        return waitForMe("Robert"); // this will result in promise rejection
    })
    .then((data) => {
        console.log(data); // this never gets executed
        return waitForMe("Eliu");
    })
    .then((data) => {
        console.log(data); // this never gets executed
    })
    .catch((error) => {
        console.log(error); // Robert is always on time
    })

记住在 promise 的链式调用中一旦有一个产生错误后续的链将会被终止。这也是为什么最后两个打印没有执行。

catch 操作符并不总是必须添加到最后,它可以添加到链式的中间然后可以捕获到它那个位置之前的错误。

const waitForMe = function (name) {
    return new Promise((resolve, reject) => {
        if (name === "Robert") {
            return reject("Robert is always on time");
        } else {
            setTimeout(() => {
                return resolve(name);
            }, 2000);
        }
    });
}

waitForMe("Parwinder")
    .then((data) => {
        console.log(data); // wait 2 second and log "Parwinder"
        return waitForMe("Lauren");
    })
    .then((data) => {
        console.log(data); // wait 2 more seconds and log "Lauren"
        return waitForMe("Robert"); // this will result in promise rejection
    })
    .catch((error) => { // catches the promise rejection
        console.log(error); // Robert is always on time
        return waitForMe("Eliu"); // continues the chain
    })
    .then((data) => {
        console.log(data); // Eliu
    })

注意: 为什么不一直使用 catch 而忽略 then 中的错误处理器呢?

我上面提到过 then 的劣势:

需要为每一个 then 添加一个错误处理器。

有时候你可能需要在链式 then 的错误处理器中有不同的错误处理方式,基于这一点,then 中独立的错误处理器可能会更有优势。

操作符

promise 上有两个重要的操作符,它们分别适应特定的场景:Promise.allPromise.race

Promise.all

当你在一个异步操作后执行另一个(串行),promise 的链式调用很顺手。经常,你需要多个异步操作并行执行而不是等一个执行完成后再执行。另外,你的操作依赖所有的异步操作的完成情况。

Promise.all 使我们可以同时执行多个异步操作,但依旧需要等到它们都完成 了才执行回调。

const waitForMe = function (name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            return resolve(name);
        }, 2000);
    });
}

const firstPromise = waitForMe("Parwinder");
const secondPromise = waitForMe("Lauren");
const thirdPromise = waitForMe("Robert");
const fourthPromise = waitForMe("Eliu");

Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise])
    .then((data) => {
        console.log(data); // [ 'Parwinder', 'Lauren', 'Robert', 'Eliu' ]
    });

上面的例子同时执行了 promise,等到它们都返回 name 就会输出一个结果的数组。这种方式执行耗费 2 秒,链式的形式则耗费 8 秒来输出四个名字。

数组中输出顺序是严格与输入 Promise.all 中的顺序是一致的。

注意: Promise.all 中即使有一个错误产生,整个结果都会失败。

const waitForMe = function (name) {
    return new Promise((resolve, reject) => {
        if (name === "Robert") {
            return reject("Robert is always on time");
        } else {
            setTimeout(() => {
                return resolve(name);
            }, 2000);
        }
    });
}

const firstPromise = waitForMe("Parwinder");
const secondPromise = waitForMe("Lauren");
const thirdPromise = waitForMe("Robert");
const fourthPromise = waitForMe("Eliu");

Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise])
    .then((data) => {
        console.log(data);
    })
    .catch((error) => {
        console.log(error); // Robert is always on time
    })

它会忽略其他成功的 promise,若有多个错误它会返回输入 Promise.all 中数组的第一个发生错误的 promise。

const waitForMe = function (name) {
    return new Promise((resolve, reject) => {
        if (name === "Robert") {
            return reject("Robert is always on time");
        } else if (name === "Lauren") {
            return reject("Lauren is always on time");
        } else {
            setTimeout(() => {
                return resolve(name);
            }, 2000);
        }
    });
}

const firstPromise = waitForMe("Parwinder");
const secondPromise = waitForMe("Lauren");
const thirdPromise = waitForMe("Robert");
const fourthPromise = waitForMe("Eliu");

Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise])
    .then((data) => {
        console.log(data);
    })
    .catch((error) => {
        console.log(error); // Lauren is always on time
    })

Promise.race

Promise.race 处理一个特殊的情形,当你需要同时执行多个异步操作,但不需要等到它们全部完成。相反,你想当第一个 promise 完成后尽快执行回调。

const waitForMe = function (name, time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            return resolve(name);
        }, time);
    });
}

const firstPromise = waitForMe("Parwinder", 4000);
const secondPromise = waitForMe("Lauren", 3000);
const thirdPromise = waitForMe("Robert", 7000);
const fourthPromise = waitForMe("Eliu", 5000);

Promise.race([firstPromise, secondPromise, thirdPromise, fourthPromise])
    .then((data) => {
        console.log(data); // Lauren
    })
    .catch((error) => {
        console.log(error);
    })

我为 setTimeout 添加了一个参数,跟着每一个名字我传入了不同的时间,"Lauren" 只有 3 秒钟所以她永远会赢得"比赛",然后打印出她的名字。

前端黑板报