手写源码系列(二)——Promise相关方法

本文首发于知乎专栏——前端面试题汇总,大家可以通过文章底部的阅读原来来访问原文地址

手写Promise相关方法

Promise是面试中经常遇到的,如果面试中面试官问你Promise.all()怎么用,那你面试的岗位可能是差不多高级前端开发的岗位,但如果让你手写一个Promise.all()那你面试的岗位应该就是资深/专家前端开发的岗位了

上期回顾

上期我们实现了函数的call()bind()apply()方法。

Promise.all()

先回顾一下Promise.all()的用法

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

例子如下:

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

手写实现Promise.all()方法

直接上代码:

Promise.myAll = function (iterators) {
  const promises = Array.from(iterators)
  const len = promises.length
  let count = 0
  let resultList = []
  return new Promise((resolve, reject) => {
    promises.forEach((p, index) => {
      Promise.resolve(p)
        .then((result) => {
          count++
          resultList[index] = result
          if (count === len) {
            resolve(resultList)
          }
        })
        .catch(e => {
          reject(e)
        })
    })
  })
}

核心思路:

  1. Promise.myAll()返回的肯定是一个promise对象,所以可以直接写一个return new Promise((resolve, reject) => {})(这应该是一个惯性思维)
  2. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  3. 关键点是何时"决议",也就是何时resolve出来,在这里做了计数器(count),每个内部promise对象决议后就将计数器加一,并判断加一后的大小是否与传入对象的数量相等,如果相等则调用resolve(),如果任何一个promise对象失败,则调用reject()方法。

一些细节:

  1. 官方规定Promise.all()接受的参数是一个可遍历的参数,所以未必一定是一个数组,所以用Array.from()转化一下
  2. 使用for…of进行遍历,因为凡是可遍历的变量应该都是部署了iterator方法,所以用for…of遍历最安全

Promise.race()

回顾一下race的用法

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

有了Promise.all()的铺垫,race就好写多了。

Promise.myRace = function (iterators) {
    return new Promise((resolve, reject) => {
        for (let p of iterators) {
            Promise.resolve(p)
                .then((result) => {
                    resolve(result)
                })
                .catch(e => {
                    reject(e)
                })
        }
    })
}

核心思路:

  1. 谁先决议那么就返回谁,所以将all的计数器和逻辑判断全部去除掉就可以了。

Promise.prototype.finally()

回顾一下正常用法。

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。

let isLoading = true;

fetch(myRequest).then(function(response) {
    var contentType = response.headers.get("content-type");
    if(contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then(function(json) { /* process your JSON further */ })
  .catch(function(error) { console.log(error); })
  .finally(function() { isLoading = false; });

手写实现

Promise.prototype.myFinally = function finallyPolyfill(callback) {
    return this.then(function(value) {
            return Promise.resolve(callback()).then(function() {
                return value;
            });
        }, function(reason) {
            return Promise.resolve(callback()).then(function() {
                Promise.reject(reason);
            });
        });
};

核心思路:

  1. 当前this指向的是当前Promise对象,所以可以直接用this.then()
  2. then回调中的两个参数,一个是成功时的回调,另一个是失败是的回调,利用这个两个参数处理当前promise对象的两种不同情况
  3. 无论如何都要调用传入的callback函数,并且将当前promise的决议值继续传递下去

一些细节:

callback传入的有可能仍然是一个Promsie对象,如果真的是Promise对象,要等该promise决议之后才能执行之后then()方法,但是这个then()中拿到的是finally()之前的决议值,有种"决议值穿透"的感觉。

PS:我在网上找到了最权威的写法,毫无破绽 https://github.com/matthew-andrews/Promise.prototype.finally/blob/master/finally.js

原文发布于微信公众号 - 较真的前端(gh_7af41a2be77e)

原文发表时间:2019-07-02

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

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券