Promise
1. 介绍#
- https://promisesaplus.com/ Promises/A+规范 
- Promise 是一个类 
- promise 有三个状态: 成功 resolve,失败 reject,等待 pending
- 用户自己可以决定成功和失败的原因。
- promise 默认执行器时立即执行
- promise 的实例都拥有一个 then 方法,一个参数是成功的回调,一个参数是失败的回调
- 如果执行函数时发生了异常也会执行失败逻辑
- 如果 promise 一旦成功就不能失败了,反过来也是一样,也就是只有 pending 状态的时候,才可以改变状态
2. 简版promise#
- 实现了基本的状态改变
- 实现了异步操作(用发布订阅模式 --- 解决同步并发问题)
// promise 的特点以及概念
// https://promisesaplus.com/  Promises/A+规范
// promise es6已经实现。ie不支持,需要polyfill
/* 
    promise的产生--->解决异步问题
    1. 多个异步请求并发(希望同步最终的结果) Promise.all
    2. 链式异步请求的问题,上一个人的输出是下一个人的输入,Promise链式调用
    3. 缺陷:还是基于回调
*/
// 实现一个简单的promise
/* 定义三种promise的状态 */
let RESOLVE = "RESOLVE";
let REJECT = "REJECT";
let PENDING = "PENDING";
class Promise {
  /* 接收一个执行器 */
  constructor(executor) {
    this.status = PENDING; // 默认状态为pending
    this.value = undefined; // 成功的信息
    this.reason = undefined; // 失败的信息
    this.onFulfilledCallback = []; // 成功回调的储存器
    this.onRejectedCallback = []; // 失败回调的储存器
    let resolve = (value) => {
      // 成功调用的方法
      if (this.status === PENDING) {
        // 如果是pending状态才需要改变状态
        this.status = RESOLVE;
        this.value = value;
        this.onFulfilledCallback.forEach((fn) => fn()); // 当调用成功resolve方法的时候,依次执行函数
      }
    };
    let reject = (reason) => {
      // 失败调用的方法
      if (this.status === PENDING) {
        this.status = REJECT;
        this.reason = reason;
        this.onRejectedCallback.forEach((fn) => fn()); // 当调用reject方法的时候,依次执行函数
      }
    };
    /* 这里用来捕获error错误 */
    try {
      executor(resolve, reject); // 会立即执行
    } catch (e) {
      reject(e);
    }
  }
  /* 每一个promise必须有一个then方法 */
  then(onFulfilled, onRejected) {
    /* 如果状态为成功,那就调用你成功的方法,传入成功的信息 */
    if (this.status === RESOLVE) {
      onFulfilled(this.value);
    }
    /* 如果状态为失败,就调用失败的方法,传入错误信息 */
    if (this.status === REJECT) {
      onRejected(this.reason);
    }
    /*  如果调用then的时候,status的状态是Pending状态说明,是异步调用,
        需要把调用的成功回调和失败回调存入队列中,当调用resolve的时候,再执行(发布订阅解决异步问题)
     */
    if (this.status === PENDING) {
      this.onFulfilledCallback.push(() => {
        // TODO:
        onFulfilled(this.value); // 切片,把函数切开,可以添加新的逻辑进去
      });
      this.onRejectedCallback.push(() => {
        // TODO:
        onRejected(this.reason);
      });
    }
  }
}
// 使用
let promise = new Promise((res, rej) => {
  //   throw new Error("报错");
  //   res("成功")
  setTimeout(() => {
    rej("失败");
  }, 2000);
});
promise.then(
  (data) => {
    console.log(data); // 成功
  },
  (err) => {
    console.log(err); // 失败
  }
);
3. 符合A+规范#
- 链式调用
- 穿透调用
- 递归返回promise调用
- 已通过测试
/**
 * @description 核心逻辑,用于处理返回值是promise还是一个普通值
 * @param {*} promise2 返回的promise
 * @param {*} x then的返回值
 * @param {*} resolve 新的promise的resolve函数
 * @param {*} reject 新的promise的reject函数
 * @returns undefined
 */
function resolvePromise(promise2, x, resolve, reject) {
  console.log(promise2, x, resolve, reject);
  // 如果 x 是当前的promise2,就直接抛出类型错误
  if (x === promise2) {
    // 直接抛出错误
    return reject(new TypeError("类型错误"));
  };
  // 如果是别人的promise可能成功后还会失败,为了确保状态只改变一次,所以要加一个锁,只能执行一次
  let called = false;
  // 如果是类promise,那么就执行返回结果
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    try {
      let then = x.then; // 只取一次then
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            // 递归,来处理如果promise里面又返回一个promise的情况
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        ); // 执行
      } else {
        // 可能是{ this: {}}
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    // 如果返回值是普通值,直接返回x就可以
    resolve(x);
  }
}
// promise
let PENDING = "PENDING";
let FULFILLED = "FULFILLED";
let REJECTED = "REJECTED";
class Promise {
  constructor(executor) {
    this.state = PENDING;
    this.result = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = (result) => {
      // 这里要用箭头函数,不然this,就不是当前的类了,会报错说拿不到state
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.result = result;
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };
    let reject = (reason) => {
      // 只有pending状态才可以转成其他状态
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    /* 处理穿透逻辑 */
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v=>v;
    onRejected = typeof onRejected === 'function' ? onRejected : e=>{throw e};
    /* 
        链式调用:调用.then之后返回一个新的promise
        1. then里面是一个成功普通值,会直接返回为下一个promise的then的成功结果
        2. 如果当前then失败,那么会走下一次的then的失败
        3. 不管当前then是成功还是失败,只要返回的是普通值,都会走下一个
    */
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
        setTimeout(() => {
          // 为了拿到promise2,用setTimeout异步获取promise2
          try {
            let x = onFulfilled(this.result); // 处理普通值。返回之后。走下一个then的成功
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 如果异常就抛出
            reject(e);
          }
        }, 0);
      }
      if (this.state === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.result);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
}
Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
module.exports = Promise;
4. 其他方法#
4.1 延迟对象#
- 自己实现的promise支持,浏览器提供的promise并不支持
Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
const fs = require('fs');
const Promise = require('./1.js');
// 利用延迟对象读取文件
function readFile(filePath, encoding) {
    let dfd = Promise.deferred();
    fs.readFile(filePath, encoding, (err, data) => {
        if(err) return dfd.reject(err);
        dfd.resolve(data);
    })
    return dfd.promise
}
readFile('a.txt', 'utf8').then(data => {
    console.log(data);
})
4.2 resolve#
- 静态方法
static resolve (data) {
  return new Promise((resolve) => {
    resolve(data)
  })
}
4.3 reject#
static reject (err) {
  return new Promise((resolve, reject) => {
    return reject(err);
  })
}
4.3 all#
- 记录下标
 static all (promises) {
    return new Promise((resolve, reject) => {
      let result = [];
      let time = 0;
      const processSucess = (index, data) => {
        result[index] = data; // 放到对应的下标中
        if(++time === result.length) {  // 当循环的次数和结果的数组长度一致就抛出结果
          resolve(result);
        }
      }
      /* 对数组进行循环 */
      for(let i = 0 ; i < promises.length; i++) {
        let p = promises[i];
        /* 判断是否是promise,如果是就取promise的结果 */
        if( p.then && typeof p.then === 'function') {
          p.then(data => {
            processSucess(i, data);
          }, reject)
        } else {
          // 如果不是直接抛出结果
          processSucess(i, p);
        }
      }
    })
  }
4.4 race#
- 竞态
static race (promises) {
  return new Promise((resolve, reject) => {
    for(let i = 0; i < promises.length; i++) {
      let p = promises[i];
      if( p && typeof p.then === 'function') {
        p.then(resolve, reject)  // 有一个成功就成功,状态成功就不会再改变了
      } else {
        resolve(p);
      }
    }
  })
}
- race应用--超时
let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功')
    }, 3000);
})
function wrap(p1) {
    let abort;
    let p = new Promise((resolve, reject) => {
        abort = reject
    })
    let p2 = Promise.race([p, p1]);
    p2.abort = abort;
    return p2;
}
let p2 = wrap(p1);
p2.then(data => {
    console.log(data);
}, (err) => {
    console.log(err);
})
setTimeout(() => {
    p2.abort('超过2秒')
}, 2000);
4.5 catch#
catch(errfn) {
  return this.then(null, errfn);
}
4.6 finally#
finally(cb) {
  return this.then((data) => {
    // 外面包一层Promise.resolve,里面返回一个pormise的话会进行等待
    // 但是finally成功并不会记录里面的结果,只会记录上一个then的结果。
    return Promise.resolve(cb()).then(() => data);
  }, (err) => {
    // 错误如果finally里面错误,外面catch会捕获到,会返回1000
    // 如果finally里面是正确的,外面catch会捕获原始的‘失败’错误。
    return Promise.resolve(cb()).then(() => {throw err});
  })
}
4.7 allSettled#
4.8 any#
4.9 promisify#
- node自带的方法
const fs = require('fs');
const util = require('util');
let readFile = util.promisify(fs.readFile);
readFile('./a.txt', 'utf8').then(data => {
    console.log(data);
})
- 实现promisify
const fs = require('fs');
function promisify(fn) {
    /* 拿到函数返回值的参数 */
    return function (...args) {
        return new Promise((resolve, reject) => {
            /* 传入参数 */
            fn(...args, (err, data) => {
                if(err) return reject(err);
                resolve(data);
            })
        })
    }
}
// 包装所有fs方法为promise
function promisifyAll(obj) {
    let o = {};
    for(let key in obj) {
        o[key + 'Promise'] = promisify(obj[key]);
    }
    return o;
}
let newFs = promisifyAll(fs);
newFs.readFilePromise('./a.txt', 'utf8').then(data => {
    console.log(data);
})