[查遗补漏] Promise

266 阅读5分钟

executor

Promise对象的构造器(constructor)语法如下:

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码,“歌手”)
});

传递给 new Promise 的函数被称为executor(执行者)。当 new Promise 被创建,executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve,如果出现 error 则调用 reject。

由 new Promise 构造器返回的 promise 对象具有以下内部属性:

  • state — 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。
  • result — 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。 所以,executor 最终将 promise 移至以下状态之一: image.png
let promise = new Promise(function(resolve, reject) {
  // 当 promise 被构造完成时,自动执行此函数

  // 1 秒后发出工作已经被完成的信号,并带有结果 "done"
  setTimeout(() => resolve("done"), 1000);
});

通过运行上面的代码,我们可以看到两件事儿:

  1. executor 被自动且立即调用(通过 new Promise)。
  2. executor 接受两个参数:resolve 和 reject。这些函数由 JavaScipt 引擎预先定义,因此我们不需要创建它们。我们只需要在 executor 准备好时调用其中之一即可。

then,catch,finally

  1. then 语法如下:
promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

.then的第一个参数是一个函数,该函数将在promise resolved后运行并接收结果。

.then的第二个参数也是一个函数,该函数将在promise rejected后运行并接收 error。

如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then 提供一个函数参数:

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1 秒后显示 "done!"
  1. catch 错误处理下面两种方式是一样的:
  • 可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)
  • 使用 .catch(errorHandlingFunction)
let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"

.catch(f)调用是.then(null, f)的完全的模拟,它只是一个简写形式。

  1. finally
  • finally 处理程序(handler)没有参数。在 finally 中,我们不知道 promise 是否成功。没关系,因为我们的任务通常是执行“常规”的定稿程序(finalizing procedures)。
    • 例如无论如何,都停止使用不再需要的loading或者indicator。
    • finally 是执行清理(cleanup)的很好的处理程序。
  • finally 处理程序将结果和 error 传递给下一个处理程序。
new Promise((resolve, reject) => {
  /* 做一些需要时间的事儿,然后调用 resolve/reject */
})
  // 在 promise 为 settled 时运行,无论成功与否
  .finally(() => stop loading indicator)
  // 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止
  .then(result => show result, err => show error)

以上:

记住以下细节:

  1. .then接收两个参数,可以只传一个参数;
  2. .catch(f)调用是.then(null, f)的完全的模拟,它只是一个简写形式;
  3. .finally是无论成功与否都会执行,并且不影响结果和error往下一个处理程序传递。

Promise类的5种静态方法

  1. Promise.all(promises) —— 等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成 Promise.all 的 error,所有其他 promise 的结果都会被忽略。
    • 如果出现 error,其他 promise 将被忽略
    • Promise.all(...) 接受可迭代对象(iterable)的 promise(大多数情况下是数组)。但是,如果这些对象中的任意一个都不是 promise,那么它将被“按原样”传递给结果数组。
  2. Promise.allSettled(promises)(ES2020 新增方法)—— 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果:
    • status: "fulfilled" 或 "rejected"
    • value(如果 fulfilled)或 reason(如果 rejected)。
  3. Promise.race(promises) —— 等待第一个 settle 的 promise,并将其 result/error 作为结果。
  4. Promise.resolve(value) —— 使用给定 value 创建一个 resolved 的 promise。
    • 当一个函数被期望返回一个 promise 时,这个方法用于兼容性。(白话:可以用这个方法Promise.resolve(value)将value “封装”进 promise,并返回一个promise
  5. Promise.reject(error) —— 使用给定 error 创建一个 rejected 的 promise。

微任务

Promise 的处理程序(handlers).then、.catch 和 .finally 都是异步的。

详见:微任务(Microtask)

自己实现Promise.all

function PromiseAll(promiseArray) {
    // 1. promise.all:返回的是 Promise 
    return new Promise((resolve, reject) => {
        // 2. 严谨判断:是否传入的参数是数组
        if(!Array.isArray(promiseArray)) {
            return reject(new Error('传入的参数必须是数组'));
        }
        const res = [];
        let counter = 0;//记录执行完了几个promise
        const promiseNums = promiseArray.length;

        for(let i = 0; i < promiseNums; i++) {
            // 3. 注意:传入的参数不一定是Promise,需要做判断
            // const isPromise = Object.prototype.toString.call(promiseArray[i] === '[object Promise]');
            // if(isPromise){
            //     promiseArray[i].then(result => {
            //         res.push(result)
            //     })
            // }else{
            //     res.push(promiseArray[i])
            // }
            // 上面这种做法的缺点:对于结果的处理需要写两遍
            // 下面的做法:Promise.resolve(params)这个参数params会被自动转换成promise
            Promise.resolve(promiseArray[i]).then(value => {
                // 4. 错误做法:忽略Promise.all的顺序问题,输入的是什么顺序,返回就是什么顺序。
                // push方法取决于执行快慢,快的就立即push,顺序可能混乱
                // res.push(value)
                // if(res.length === promiseNums){
                //     resolve(res)
                // }
                counter ++;  // 每一个promise执行完后counter+1
                res[i] = value; // 给res通过索引进行赋值

                if(counter === promiseNums){
                    resolve(res)
                }
                // 5. 不能使用res.length === promiseNums来判断
                // 因为数组的特性
                // const array = [];
                // array[6] = 'xxxx'; 给最后一个索引赋值,会按照最后一个索引这么长的空间
                // console.log(array.length); //7

            }).catch(e => reject(e));

        }
    })
}

const pro1 = new Promise((res,rej)=>{
    setTimeout(() => {
        res('1')
    }, 1000);
})
const pro2 = new Promise((res,rej)=>{
    setTimeout(() => {
        res('2')
    }, 1000);
})
const pro3 = new Promise((res,rej)=>{
    setTimeout(() => {
        res('3')
    }, 1000);
})

const proAll = PromiseAll([pro1,pro2,pro3])
    .then(res => {
        console.log(res) // 3秒之后打印['1','2','3']
    }).catch((e)=>{
        console.log(e)
    })

参见