Node基础知识

97 阅读14分钟

高阶函数

在函数的执行前后添加自定义行为

// 什么是高阶函数?  1.一个函数返回一个函数   2.一个函数可以接收一个参数是函数

// 利用高阶函数可以处理哪些问题  1) 扩展方法
function say(args) { // 我们需要对say方法进行扩展,但是不能修改源代码
    console.log('say', args)
}
Function.prototype.before = function (cb) {
    return (...args) => { // newSay
        cb();
        this(...args); // 扩展原来的函数
    }
}
let newSay = say.before(() => {
    console.log('beforeSay')
})
newSay('hello');


// 我们可以通过高阶函数来实现参数的保留
// 判断一个变量的类型有: typeof只能判断基础类型  instanceof 判断实例类型  constuctor可以看当前实例是由谁构造出来的
// Object.prototype.toString.call

function isType(typing) {
    return (val) => {
        return Object.prototype.toString.call(val) === `[object ${typing}]`
    }
}
// 利用高阶函数保留参数变量  ->  (函数柯里化, 函数的反柯里化)
let isString = isType('String'); // 比较就是函数声明的作用域和执行的作用域是不一样的,这时候就会导致闭包
console.log(isString('hello'));
console.log(isString(123));


// 函数柯里化 就是将对个参转化成一次传入一个参数
// 异步编程问题 主要有一个并发处理的问题

after方法封装

// 前端常见的就是 同时发送多个请求,最终拿到多个请求的返回值 来进行渲染页面

const fs = require('fs'); //node中自带核心模块  
const path = require('path'); 
// js在执行的时候会有一个事件环的机制 默认先执行当前的上下文
// let school = {}
// function done(){
//     if(Object.keys(school).length === 2){
//         console.log(school)
//     }
// }
// 你不知道的javascript 开发者的理解
function after(times,callback){ // 暂存times 同时返回一个新的函数
    const obj = {}
    return function(value,key){ // done
        obj[key] = value;
        if(--times === 0){
            callback(obj);
        }
    }
}
const done = after(2,(obj)=>{ // 调用done两次后再去执行对应的回调
    console.log(obj);
})
fs.readFile(path.resolve(__dirname,'name.txt'),'utf8',function(err,data){
    done(data,'name')
})
fs.readFile(path.resolve(__dirname,'age.txt'),'utf8',function(err,data){
    done(data,'age')
})

// 将name和age放到同一个对象中

发布/订阅

const fs = require('fs');
const path = require('path');
let events = {
    _events:[], // 如果events中存放的是promise compose
    on(cb){
        this._events.push(cb);
    },
    emit(...args){
        this._events.forEach(cb=>cb(...args))
    }
};
// 所谓的订阅就是把事情 存到一个列表中,每次发布将列表中的函数依次执行
events.on(function(key){ // 每次发布都执行以下此函数
  console.log('读取完毕了一次',key)
})
let school = {}
events.on(function(key,data){ // 每次发布都执行以下此函数
    school[key] = data;
    if(Object.keys(school).length === 2){
        console.log(school);
    }
})
fs.readFile(path.resolve(__dirname, 'name.txt'), 'utf8', function (err, data) {
    events.emit('name',data)
})
fs.readFile(path.resolve(__dirname, 'age.txt'), 'utf8', function (err, data) {
    events.emit('age',data)
})
// 观察者模式和发布订阅模式的区别
// 发布和订阅之间没有耦合关系, 什么时候发布是取决于自己的
// 观察者模式 观察者,被观察者。 如果被观察者发生了变化,会主动通知观察者去更新 (收集:被观察者要收集观察者)
// 观察者模式是包含发布订阅的

观察者模式

// 被观察者类
class Subject {
    constructor(name) {
        this.name = name;
        this.observers = [];
        this.state = '开心'
    }
    attach(o) {
        this.observers.push(o); // 被观察者会收集所有的观察者
    }
    setState(newState) {
        this.state = newState;
        this.observers.forEach(o => o.update(this)); // 通知所有的观察者我发生了变化
    }
}
// 观察者类
class Observer {
    constructor(name) {
        this.name = name
    }
    update(s) {
        console.log(`我是${this.name}:当前宝宝的状态`, s.state)
    }
}
const baby = new Subject('小宝宝')
const m = new Observer('妈妈');
const f = new Observer('爸爸')
baby.attach(m);
baby.attach(f);
baby.setState('不开心')
setTimeout(()=>{
    baby.setState('开心了')
},1000)
// 我家有个小宝宝 自己的状态就是是否开心, 我和我媳妇要监控小宝宝的状态, 稍后小宝宝不开心了 会通知我们

promise

使用及规范:

// Promise 默认是一个类 用的时候需要new ,而且创建的实例上都有一个then方法. 在new过程中需要传入一个执行器

// 1) promise中有一个value属性用来描述成功的原因  reason是一个失败的原因
// 2) promise中如果出现异常也会执行失败的逻辑
// 3) promise有三个状态 pending 既不成功也不失败  fulfilled 成功态  rejected失败态
// 4)当状态是pending的时候 可以转化成成功或者失败,否则不能去改变状态
// 5) executor会立刻执行,并且传入两个参数resolve, reject 

const Promise = require('./my-promise/promise')
const p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        reject('成功')
    },1000);
});
p1.then((value) => { // then里面要有两个参数  onFulfilled, onRejected
    console.log('成功',value)
}, (reason) => {
    console.log('失败',reason)
})

p1.then((value) => { // then里面要有两个参数  onFulfilled, onRejected
    console.log('成功',value)
}, (reason) => {
    console.log('失败',reason)
})


// 实现promise的链式调用

有一点需要注意,在链式调用的过程中,无论前一个then返回的promise走到了成功回调还是失败回调,接下来的promise总是走成功回调:

    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(10)
      }, 1000)
    }).then(res => {
      console.log('第1轮', 'go succ callback,', res)
      return 'succ'
    }, res => {
      console.log('第1轮', 'go fail callback,', res)
      return 'fail'
    }).then(res => {
      console.log('第2轮', 'go succ callback,', res)
      return 'succ'
    }, res => {
      console.log('第2轮', 'go fail callback,', res)
      return 'fail'
    }).then(res => {
      console.log('第3轮', 'go succ callback,', res)
      return 'succ'
    }, res => {
      console.log('第3轮', 'go fail callback,', res)
      return 'fail'
    })

执行结果为:

1go fail callback, 102go succ callback, fail
第3go succ callback, succ

从执行结果中可以看到,最开始new出来的那个Promise在setTimeout里面执行了reject,所以这个promise就变成了rejected状态,进而执行了失败回调,因此console打印出了

1go fail callback, 10

但后面的promise依然执行了成功回调

当然,如果在回调中throw出来了Error,无论是从成功回调中throw的,还是从失败回调中throw的,都会走到失败回调,例如:

    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(10)
      }, 1000)
    }).then(res => {
      console.log('第1轮', 'go succ callback', res)
      return 'succ'
    }, res => {
      console.log('第1轮', 'go fail callback', res)
      return 'fail'
    }).then(res => {
      console.log('第2轮', 'go succ callback', res)
      throw new Error('aaa');
      return 'succ'
    }, res => {
      console.log('第2轮', 'go fail callback', res)
      return 'fail'
    }).then(res => {
      console.log('第3轮', 'go succ callback', res)
      return 'succ'
    }, res => {
      console.log('第3轮', 'go fail callback', res)
      return 'fail'
    })

就会在控制台打出如下信息:

1go fail callback 102go succ callback fail
第3go fail callback Error: aaa
    at sn (1.html:21:13)

但是,如果在then链条中,没有传失败回调的话,需要将reject传递到之后的promise的失败回调中,例如:

    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(10)
      }, 1000)
    }).then(res => {
      console.log('第1轮', 'go succ callback', res)
      return 'succ'
    }).then(res => {
      console.log('第2轮', 'go succ callback', res)
      return 'succ'
    }, res => {
      console.log('第2轮', 'go fail callback', res)
      return 'fail'
    }).then(res => {
      console.log('第3轮', 'go succ callback', res)
      return 'succ'
    }, res => {
      console.log('第3轮', 'go fail callback', res)
      return 'fail'
    })

打印出的结果为:

135****89602go fail callback 103go succ callback fail

new出的Promise在调用then添加回调的时候没有绑定失败回调,因此new Promise里reject后,就会传导到下一个promise的then绑定的失败回调中去,所以输出了

2go fail callback 10

基本实现:

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
    constructor(executor) {
        this.status = PENDING;// promise的默认状态
        this.value = undefined; // 成功的值和失败的原因 
        this.reason = undefined;

        this.onResolvedCallbacks = []; // 这里存放所有成功的回调
        this.onRejectedCallbacks = []; // 所有失败的回调
        const resolve = (value) => { // 更改状态的方法 resolve
            if(this.status == PENDING){
                this.value = value;
                this.status = FULFILLED
                this.onResolvedCallbacks.forEach(cb=>cb(this.value))
            }
        }
        const reject = (reason) => { // 更改状态的方法 reject
            if(this.status === PENDING){
                this.reason = reason;
                this.status = REJECTED
                this.onRejectedCallbacks.forEach(cb=>cb(this.reason))
            }
        }
        try{
            executor(resolve, reject); // executor就是执行器立刻执行,出错就调用reject
        }catch(e){
            reject(e);
        }
    }
    then(onFulfilled, onRejected){ // 调用then的时候会判断是成功还是失败
        if(this.status === FULFILLED){
            onFulfilled(this.value);
        }
        if(this.status === REJECTED){
            onRejected(this.reason)
        }
        if(this.status == PENDING){
            // 发布订阅  有可能调用then的时候没成功也没失败,我就将回调存起来,稍后根据用户调用的方法在进行执行
            this.onResolvedCallbacks.push(onFulfilled);
            this.onRejectedCallbacks.push(onRejected)
        }
    }
}

module.exports = Promise;

支持链式调用:

1.promise中then传入的方法 返回的如果不是promise那么会将这个结果传递给下一次then的成功中去

2.如果then传入的方法在执行的时候出错了 会执行下一次then的失败

3.如果then传入的方法在执行返回的是一个promise,那么会根据promise的状态来决定走下一次then的成功还是失败.成功的值和失败的原因会以这个promise的结果为准

什么情况会走失败?

1) 抛出异常

2) 如果返回的是一个失败的promise会走失败

其他情况全部走成功

使用案例:

const fs = require('fs');
const path = require('path');

// 希望解决回调地狱的问题,恶魔金字塔。 就采用promise来解决这个问题

function readFile(url,encoding){
    return new Promise((resolve,reject)=>{
        fs.readFile(url,encoding,function(err,data){
            if(err) return reject(err);
            resolve(data)
        })
    })
}
// 1.promise中then传入的方法 返回的如果不是promise那么会将这个结果传递给下一次then的成功中去
// 2.如果then传入的方法在执行的时候出错了 会执行下一次then的失败
// 3.如果then传入的方法在执行返回的是一个promise,那么会根据promise的状态来决定走下一次then的成功还是失败.成功的值和失败的原因会以这个promise的结果为准

// 什么情况会走失败?1) 抛出异常  2) 如果返回的是一个失败的promise会走失败
// 其他情况全部走成功
readFile(path.resolve(__dirname,'file.txt'),'utf8').then(data=>{
    return readFile(path.resolve(__dirname,data+1),'utf8')
}).then((data)=>{
    console.log(data,'s');
},(err=>{
    console.log(err,'e')
})).then(()=>{
    console.log('成功')
},()=>{
    console.log('失败')
})

实现:

then需要返回一个promise,由于传入Promise构造函数的回调是会立即执行的,所以我们可以把then里面的发布、订阅这些操作放到返回的这个promise的初始化回调函数中,这样我们就可以很方便的将订阅的函数的返回值给下一个promise传过去:

then(onFulfilled, onRejected){
  let p1 = new Promise((resolve, reject) => {
    // x是一个普通值 则将这个值直接传入到resolve函数中即可
    if(this.status === FULFILLED){
      let x = onFulfilled(this.value);
      resolve(x);
    }
    if(this.status === REJECTED){
      let x = onRejected(this.reason);
      resolve(x);
    }
    if(this.status === PENDING){
      this.onResolvedCallbacks.push(() => {
        let x = onFulfilled(this.value);
        resolve(x);
      });
      this.onRejectedCallbacks.push(() => {
        let x = onRejected(this.reason);
        resolve(x);
      })
    }
  })
}

这样对于订阅了有异步操作的方法,如:

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('123')
  }, 1000)
}).then((data) => {
  return data
})

p1.then((data) => {
    console.log(data, '000')
}, err => {
    console.log(err, 'err');
});

为表述方便,此处将含有定时器的Promise命名为p0

上述Promise实现的思路就是,将p0的发布和p1的订阅放在一个回调中执行,这也是在then方法中自定义push进去的方法的用意所在,这样即可保证链式调用的衔接

通过then添加的,无论是成功还是失败的回调,只要出现异常,该then方法返回的promise就要走失败的一系列回调

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('123')
  }, 1000)
}).then((data) => {
  throw new Error('fail')
})

p1.then((data) => {
    console.log(data, '000')
}, err => {
    console.log(err, 'err');
});

上面案例中,console应该打印出err

因此我们需要在回调执行时,加上catch

then(onFulfilled, onRejected){
  let p1 = new Promise((resolve, reject) => {
    // x是一个普通值 则将这个值直接传入到resolve函数中即可
    if(this.status === FULFILLED){
	    try {
        let x = onFulfilled(this.value);
        resolve(x);
      } catch (e) {
      	reject(e);
      }
    }
    if(this.status === REJECTED){
	    try {
        let x = onRejected(this.reason);
        resolve(x);
      } catch (e) {
      	reject(e);
      }
    }
    if(this.status === PENDING){
      this.onResolvedCallbacks.push(() => {
        try {
          let x = onFulfilled(this.value);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      });
      this.onRejectedCallbacks.push(() => {
        try {
          let x = onRejected(this.reason);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      })
    }
  })
}

在then中订阅的方法,除了可以返回一个普通的值之外,还可以返回一个promise实例

这时,我们需要单独封装一个方法来对resolve的执行进行一些处理,这个处理主要包含将p1的resolve的时机,延迟到返回值x这个promise对象的resolve之后(或者说将p1的resolve作为一个方法订阅到x的回调列表中去),为此我们要在p1 new的构造函数中使用p1,如果不做其他处理,这种操作是不被允许的,所以我们需要包装一层定时器

    then(onFulfilled, onRejected) { // 调用then的时候会判断是成功还是失败
        // 可以不停的then下去
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
        // mutationObserver
        let p1 = new Promise((resolve, reject) => {
            // x是一个普通值 则将这个值直接传入到resolve函数中即可
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(p1, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                })
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(p1, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                })
            }
            if (this.status == PENDING) {
                // 发布订阅  有可能调用then的时候没成功也没失败,我就将回调存起来,稍后根据用户调用的方法在进行执行
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(p1, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(p1, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    })
                })
            }
        });
        return p1; // 这里没有返回this
    }
function resolvePromise(promise, x, resolve, reject) { // x可能是别人家的promise
    // 用x的值来决定promise走 resolve还是reject
    // 核心就是用x 来处理promise是成功还是失败 
    // 我们要考虑不同人写的promise可以互相兼容,所以这里要按照规范来实现,保证promise直接可以互相调用

    // 判断x 是不是一个promise 是promise就采用他的状态,如果解析后还是promise会递归解析
    if (promise == x) {
        return reject(new TypeError(`TypeError: Chaining cycle detected for promise #<myPromise> `));
    }
    // 判断x 是不是一个promise, 如果不是promise,则直接用这个值将promise变成成功态即可
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let called = false;
        try {
            let then = x.then; // 这个x可能是通过defineProperty定义的then
            if (typeof then === 'function') { // 这已经最小判断
                // x.then((y)=>{},r=>{}) // x.then 会再去取then
                // 这个then方法可能是别人家的promise, 没有处理同时调用成功和失败方法
                then.call(x, y => { // 如果x是一个promise就用他的状态来决定 走成功还是失败
                    if (called) return;
                    called = true
                    resolvePromise(promise, y, resolve, reject); //递归解析y的值
                }, r => { // 一旦失败了 就不在解析失败的结果了
                    if (called) return;
                    called = true
                    reject(r)
                })
            } else {
                // {} / function 没有then方法 依旧是普通值  {then:123}
                resolve(x)
            }
        } catch (e) {
            if (called) return;
            called = true
            reject(e);
        }
    } else {
        // 不是对象和函数 普通值
        resolve(x)
    }
}

测试我们自己写的promise是否符合promise a+规范:

npm install promises-aplus-test -g 全局安装都是在命令行中使用

执行如下命令:promises-aplus-test 测试的文件名

比较老的一些库中,有deferred这样一个属性:

Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}

使用案例:

const Promise1 = require('./my-promise/promise');
function readFile(url, encoding) {
    // 延迟对象的使用场景
    let dfd = Promise1.deferred();
    fs.readFile(path.resolve(__dirname, url), encoding, function (err, data) {
        if (err) return dfd.reject(err);
        dfd.resolve(data);
    })
    return dfd.promise
}
readFile('file.txt', 'utf8').then(data => {
    console.log(data)
})

我们经常通过Promise.resolve('xxx')来直接得到一个执行过resolve的Promise,可以添加如下静态方法来实现

class Promise {
  	//...
    // 类本身调用的叫静态方法
    static resolve(value) {
        return new Promise((resolve, reject) => {
            resolve(value)
        })
    }
    static reject(value) {
        // 默认创建一个失败的promise
        return new Promise((resolve, reject) => {
            reject(value)
        })
    }

如果只希望订阅一个失败回调,可以通过catch简化订阅的方式,而不必通过then传两个参数,第一个放一个null在那里占位

    catch(errCallback) {
        return this.then(null, errCallback)
    }

Promise.all

    static all = function (promises) {
        let result = [];
        let times = 0;
        return new Promise((resolve, reject) => {
            function processResult(data, index) {
                result[index] = data; // 映射结果
                if (++times == promises.length) {
                    resolve(result);
                }
            }
            for (let i = 0; i < promises.length; i++) {
                let promise = promises[i];
                Promise.resolve(promise).then((data) => {
                    processResult(data, i);
                }, reject)
            }
        })
    }

注意:第7行不能写成result.length === promises.length这样来比较,

原因:假设有3个promise,result初始为空数组,如果第2个promise先resolve了,则会执行result[2] = xxx,然后result数组就会成为

[empty x 2, xxx]

但它的length是3

还有,在第13行中,调用Promise.resolve的时候直接传入了一个Promise类型的值:promise,沿着Promise.resolve这个静态方法走,这接下来会走到我们的构造函数中的resolve中去,我们需要对resolve的形参类型做一下兼容:

class Promise {
    constructor(executor) {
        this.status = PENDING;// promise的默认状态
        this.value = undefined; // 成功的值和失败的原因 
        this.reason = undefined;

        this.onResolvedCallbacks = []; // 这里存放所有成功的回调
        this.onRejectedCallbacks = []; // 所有失败的回调
        const resolve = (value) => { // 更改状态的方法 resolve
            if (value instanceof Promise) { // 这个不属于规范
                return value.then(resolve, reject)
            }
            if (this.status == PENDING) {
                this.value = value;
                this.status = FULFILLED
                this.onResolvedCallbacks.forEach(cb => cb(this.value))
            }
        }

上述代码中,第10行value instanceof Promise这个条件将会是true,然后我们将resolve和reject再订阅给value,接下来就会走到13行的条件里了

node中util.promisify将回调类api转换为promise

let util = require('util'); // 工具方法
let readFile = util.promisify(fs.readFile); // 仅仅针对node中的api 因为node中的回调方法 都有err和data

使用方式:

Promise.all([readFile(path.resolve(__dirname, 'name.txt'), 'utf8'), readFile(path.resolve(__dirname, 'age.txt'), 'utf8'), '123']).then(data => {
    console.log(data)
})

可以猜测到,promisify的实现方式,用到了高阶函数

function promisify(fn) { // fs.readFile
    return function (...args) { // readFile
        return new Promise((resolve, reject) => {
            fn(...args, function (err, data) {
                if (err) return reject(err);
                resolve(data)
            })
        })
    }
}

将fs下所有方法都转成promise:

let fsPromises = promisifyAll(fs);
function promisifyAll(obj) {
    let result = {}
    for (let key in obj) { // 判断值是不是一个函数,是函数就转化成promise
        result[key] = typeof obj[key] === 'function' ? promisify(obj[key]) : obj[key]
    }
    return result;
}
Promise.all([fsPromises.readFile(path.resolve(__dirname, 'name.txt'), 'utf8'), fsPromises.readFile(path.resolve(__dirname, 'age.txt'), 'utf8'), '123']).then(data => {
    console.log(data)
})

race方法:

赛跑就是以第一个结果为基准, 取最先的结果为准,其他的代码还会执行,只是不采用结果了

Promise.race = function (promises) {
	return new Promise(resolve, reject) {
  	for (let i = 0; i < promises.length; i++) {
    	Promise.resolve(promises[i]).then(resolve, reject)
    }
  }
}

race的使用案例:我们可能会有一个超时逻辑,超过1s 就不采用成功的结果了

假设某个promise做加载工作,耗时2s返回了结果:

let abort
let p = new Promise((resolve, reject) => {
    abort = reject;
    setTimeout(() => {
        resolve('data~~~');
    }, 2000);
});
setTimeout(() => {
    abort('超时了'); // 这里只是借助了promise一旦失败就不能成功了,但是promise原有的逻辑还是会执行
}, 1000)

但是这样一来就会出现abort这样的全局变量,所以这个逻辑需要封装一下:

用户自己的promise是p,经过wrapPromise对p包装后,再将p重新赋值,给p添加了abort方法,这个abort指向内部promise的reject

看userPromise和internalPromise哪个先有结果:

如果先调用了超时,则internalPromise先有结果,这个结果是失败,userPromise就不再执行

如果我们将setTimeout的时间改成3000,则userPromise先有结果,这个结果是成功,userPromise的resolve被执行

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('data~~~');
    }, 2000);
});
setTimeout(() => {
    p.abort('超时了'); // 这里只是借助了promise一旦失败就不能成功了,但是promise原有的逻辑还是会执行
}, 1000)
// 我们可能会有一个超时逻辑,超过1s 就不采用成功的结果了
function wrapPromise(userPromise) {
    // race 有任何一个失败了就失败 [自己造一个promise,userPromise]
    let abort;
    let internalPromise = new Promise((resolve, reject) => {
        abort = reject;
    })
    let racePromise = Promise.race([internalPromise,userPromise]);
    racePromise.abort = abort;
    return racePromise
}
p = wrapPromise(p);
p.then(data => {
    console.log(data);
}).catch(err => {
    console.log(err);
});

Promise.allSettled方法,无论成功还是失败,都将结果返回

Promise.allSettled([
    fsPromises.readFile(path.resolve(__dirname, 'file.txt'), 'utf8'),
    fsPromises.readFile(path.resolve(__dirname, 'file1.txt'))]).then(data => {
        console.log(data)
    })

file1.txt这个文件不存在,但then的data参数中依然会打印出所有promise的结果

Promise.allSettled = function (promises) {
    let result = [];
    let times = 0;
    return new Promise((resolve, reject) => {
        function processResult(data, index, status) {
            result[index] = { status, value: data }; // 映射结果
            if (++times == promises.length) {
                resolve(result);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            let promise = promises[i];
            Promise.resolve(promise).then((data) => {
                processResult(data, i, 'fulfilled');
            }, (err) => {
                processResult(err, i, 'rejected');
            })
        }
    })
}

finally:

// finnaly 无论成功和失败都执行的方法
Promise.prototype.finally = function (finallyCallback) {
    return this.then((data) => {
        return Promise.resolve(finallyCallback()).then(() => data);
    }, (err) => {
        return Promise.resolve(finallyCallback()).then(() => { throw err })
    })
}

路径相关

let path = require('path');
//从当前路径出发,得到一个绝对路径
//C:\aproject\zhufengwebpack202011\10.hmr9000\doc\b
console.log(path.resolve('b'));
//解析模块路径
//c:\aproject\zhufengwebpack202011\10.hmr9000\node_modules\webpack\lib\index.js
console.log(require.resolve('webpack'));

path只是做拼接,不管这个文件是不是真正存在

而resolve会检查模块是不是存在