首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实现Promise其它API

实现Promise其它API

作者头像
多云转晴
发布2020-08-18 14:27:00
4810
发布2020-08-18 14:27:00
举报
文章被收录于专栏:webTowerwebTowerwebTower

上一篇我们手动实现了符合 Promise/A+ 规范的代码。而 then 方法基本就是 promise 的全部内容,至于 catchresolvereject 等方法都是在原有的基础上做的扩展或者封装,这些方法并不算是 promise 的核心。下面就一一实现这些方法。

catch

catch 是 promise 实例上的方法,添加一个拒绝态的回调到当前 promise,然后返回一个新的 promise。实现如下:

catch(errCbs){
    return this.then(null, errCbs);
}

resolve 与 reject

这两个方法是 Promise 的静态方法。resolve 返回一个 Promise 对象,这样就能将该值以 Promise 对象形式使用;reject 返回一个状态为失败的 Promise 对象,并将给定的失败信息传递给对应的处理方法。

它们的实现如下:

static resolve(value){
    return new Promise((resolve, reject) => {
        resolve(value);
    });
}

static reject(reason){
    return new Promise((resolve, reject) => {
        reject(reason);
    });
}

resolve 函数比较特别,例如:

// resolve 函数可以嵌套,多层 resolve,会以最内层的值为准
//(最内层也可以是 reject,这样会走失败态)
Promise.resolve(
    Promise.resolve(
        Promise.resolve(1)
    )
).then(d => {   // d: 1
    console.log('d: ', d);
}, e => console.log('e == ', e));

这时就要改造一下 constructor 中的 resolve 函数,需要判断传入的 value 是不是一个 promise。

constructor(exector){
    var reject = (reason) => {
        // ...
    };
    var resolve = (value) => {
        if(value instanceof Promise){
            return value.then(resolve, reject);
        }
        // ...
    };
}

finally

finally 是 promise 实例上的方法,它可以传入一个回调函数,无论成功还是失败这个回调都会去执行。finally 回调参数可以返回一个 promise,如果是成功的 promise,会采用上一次的结果,如果是失败的 promise,会采用这一次的失败结果,并把结果传入 catch 中(或 then 的第二个回调参数)。finally 最终也是返回新的 promise 实例,例如:

new Promise ((resolve, reject) => {
    resolve(1);     // 首次成功
}).finally(() => {
    return new Promise((resolve) => {
        resolve(100);   // 返回成功的 promise
    });
}).then(d => {      // d == 1
    // 采用上一次的结果(1),而不是 100
    console.log('d == ', d);
}).catch(err => {
    console.log('err', err);
});

// 失败情况:

new Promise ((resolve, reject) => {
    reject(1);      // 首次
}).finally(() => {
    return new Promise((resolve, reject) => {
        reject(100);    // 失败的 promise
    });
}).then(d => {
    console.log('d == ', d);
}).catch(err => {   // err 100
    console.log('err', err);
    // 采用当前的失败数据
});

finally 方法实现如下:

finally(cb){
    return this.then(val => {
        return Promise.resolve(cb()).then(() => val);
    }, err => {
        return Promise.resolve(cb()).then(() => { throw err });
    });
}

上面的代码可能会有些困惑,成功与失败的回调都是在一个 then 中调用的,为何失败时会是新的值,而成功时会是上一次的值?成功时的回调很容易理解,失败回调是这么回事:cb 在执行时,因为是失败态,因此不会走后面 .then 的第一个回调,而是走失败的回调,这与下面的代码相似:

Promise.resolve(
    new Promise((resolve, reject) => {
        reject(100); // 失败了
    })
).then(res => {
    throw res;
}).then(res => {
    console.log('res == ', res);
}, err => {  // 成功态不会执行,而是执行失败态
    console.log('err == ', err); // err == 100
})

all

all 是一个静态方法,接受一个迭代器,返回 promise 实例,此实例在迭代所有的 promise 都完成(resolved)或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。实现如下:

static all(iterable){
    return new Promise((resolve, reject) => {
        let orderIdx = 0;
        let resultArr = [];

        for(let i = 0;i < iterable.length;i ++){
            let value = iterable[i];
            this.resolve(value).then(res => {
                resultArr[i] = res;
                orderIdx ++;
                if(orderIdx === iterable.length){
                    resolve(resultArr);
                }
            },err => {
                reject(err);
            });
        }
    });
}

测试:

let p1 = Promise.resolve(1);
let p2 = Promise.reject(2);
let p3 = Promise.resolve(3);

Promise.all([p1,p2,p3,4]).then(result => {
    console.log('result: ', result);
},err => {      // err: 2
    console.log('err: ', err);
});

all 方法通常用于异步并发场景中,例如并发的上传或下载图片,就可以把所有请求封装成 promsie 的形式存入数组中,然后使用 all 方法执行每个 promise。

race

race 也是一个静态方法,它也接受一个迭代器,返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

实现:

static race(iterable) {
    if(!Array.isArray(iterable)){
        throw TypeError(`${iterable} is not an array`);
    }
    let Constructor = this;
    return new Constructor((resolve, reject) => {
        for(let i = 0;i < iterable.length;i ++){
            Constructor.resolve(iterable[i]).then(resolve,reject);
        }
    });
}

race 方法可能会 resolve,也可能会 reject,这取决于谁会先执行完毕,是一种“竞争”关系。可以使用 race 实现一个这样的场景:在规定的时间内要 resolve,不然就执行 reject。

最后

Promise 是解决异步回调问题的利器,使用 promise 可以把嵌套式的函数调用写成链式调用,有利于代码的阅读,有了 promise,我们可以把很多异步函数封装成 promise 风格的函数。比如 Node 中的 promisify 方法,它传入一个遵循常见的错误优先的回调风格的函数(即以 (err, value) => ... 回调作为最后一个参数),并返回一个返回 promise 的版本。例如:

const utils = require('util');
const fs = require('fs');
const readFile = utils.promisify(fs.readFile);

readFile('./util.js', { encoding: 'utf-8' }).then(data => {
    console.log(data);
}).catch(err => {
    console.log(err);
});

实现 promisify 函数

代码如下:

function promisify(fn){
    return (...args) => {
        return new Promise((resolve, reject) => {
            fn(...args, (err, result) => {
                if(err) reject(err);
                else resolve(result);
            });
        });
    }
}

resolve 函数只能传入一个值,因此对应返回多个参数的回调函数,promisify 是不能转换的,同时还要注意进行转换的函数是否包含 this 的引用。

在上一篇我们实现的 then 方法是使用 setTimeout 实现的,它是宏任务的一个异步函数,而 ES6 中的 then 方法是内部是微任务实现的。典型的例子如下:

function loop(){
    console.log(111);
    Promise.resolve().then(loop);
}
loop();

这段代码如果是 ES6 中的 promise,在浏览器中运行时会发现页面将崩溃,这是因为微任务在执行时会把微任务队列中的任务全部执行完,上面代码显然是执行不完的,因为每次调用完 then 后,又会递归调用 then,微任务队列永远清不空!而如果是宏任务实现的 then 方法,因为宏任务队列一次只执行一个任务,因此上面的代码会像计时器一样一直执行,但不会造成死循环,每次执行一个宏任务后,浏览器会转而执行其他的任务,比如微任务、页面渲染。如果要用微任务实现 promise,可以用浏览器端的 MutainObserver 或者 Node 中的 process.nextTick

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebTower 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • catch
  • resolve 与 reject
  • finally
  • all
  • race
  • 最后
    • 实现 promisify 函数
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档