专栏首页多云转晴实现Promise其它API

实现Promise其它API

上一篇我们手动实现了符合 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

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Promise其它方法二

    race静态方法,只要有一个改变状态,不管成功失败都返回这个改变状态的值,如果是普通值之间成功返回:

    wade
  • Promise其它方法一

    这个是最简单的,catch其实就是没有成功方法resolve的then函数,所以catch方法:

    wade
  • 原 Promise 实现

    魂祭心
  • JavaScript 异步编程指南 — Give me a Promise

    随着 ES6 标准的出现,给我们带来了一个新的异步解决方案 Promise。目前绝大多数 JavaScript 新增的异步 API 无论是在浏览器端还是 Nod...

    一只图雀
  • ES6 Promise 的最佳实践

    ES6 promise 是非常棒的一个功能, 它是 JavaScript 异步编程中不可或缺的部分,并且取代了以 回调地狱而闻名的基于回调的模式。

    秋风的笔记
  • 一个小白的角度看JavaScript Promise 完整指南

    这篇文章算是 JavaScript Promises 比较全面的教程,该文介绍了必要的方法,例如 then,catch和finally。此外,还包括处理更复杂的...

    胡哥有话说
  • 初学者应该看的JavaScript Promise 完整指南

    这篇文章算是 JavaScript Promises 比较全面的教程,该文介绍了必要的方法,例如 then,catch和finally。 此外,还包括处理更复杂...

    前端小智@大迁世界
  • 微信小程序中使用Promise进行异步流程处理

    我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理。比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环...

    一斤代码
  • 如何更好的取消一个promise?

    其实就像一个执行中的ajax要被取消一样,ajax有abort()进行取消,而且fetch api 也有了相关的规范-【AbortController】。

    zz_jesse

扫码关注云+社区

领取腾讯云代金券