2. promise相关方法

436 阅读5分钟

Promise.deferred

  • 原生没有这个方法
// Promise.deferred 可以减少一层promise嵌套 原生没有这个方法

// 用promise包了一层
function readFile(...args) {
  return new Promise((resolve, reject) => {
    fs.readFile(...args, (err,data) => {
      if(err) return reject(err);
      resolve(data);
    })
  })
}

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

function readFile(...args) {
  let dfd = Promise.deferred(); //创建了一个promise
  fs.readFile(...args, function(err, data) {
    if(err) return dfd.reject(err);
    dfd.resolve(data);
  })
  return dfd.promise; // 返回promise实例
}

catch

readFile("./xxx").then(data => {
  console.log(data);
})
.catch(err => {
  console.log(err);
})

// catch捕获错误,后面可以继续调用then方法
// promise实例方法
class Promise {
  catch(errFn) {
    return this.then(null, errFn)
  }
}

静态方法

  • Promise.resolve
  • Promise.reject
Promise.resolve("hello").then(data => {
  console.log(data); //'hello'
});
Promise.reject("hello").catch(err => {
  console.log(err); // 'hello'
});

// 两者的差异在于resolve具有等待的效果
// 与上面的不同是传递的字符串改成promise实例
Promise.resolve(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hello')
  }, 1000)
})).then(data => {
  console.log(data); // 等待promise执行完成, 输出‘hello’
});
Promise.reject(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hello')
  }, 1000)
})).catch(err => {
  console.log(err); // promise{ <pending> } 不等待
});

// 实现
class Promise{
  constructor(executor) {
    const resolve = (value) => {
      // 传入的value有可能是promise
      // 这里增加的逻辑不属于规范中,只是为了和原生的promise表现形式一样
      if(value instanceof Promise) {
        return value.then(resolve, reject); 
      }
    }
  }

  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value);
    })
  }
  static reject(err) {
    return new Promise((resolve, reject) => {
      reject(err);
    })
  }
}
  • Promise.all
  • 原型方法finally
const fs = require("fs").promises;
Promise.all([fs.readFile("name.txt", "utf8"), fs.readFile("age.txt", "utf8")])
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err);
  });

// Promise.all 表示全部成功才成功 一个失败就失败
// Promise.allSettled 无论成功失败都拿到全部结果
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let result = [];
    let index = 0;
    function process(value, key) {
      result[key] = value;
      if (++index === promises.length) {
        // 靠计时器解决多个异步并发问题
        resolve(result);
      }
    }
    for (let i = 0; i < promises.length; i++) {
      let p = promises[i];
      if (p && typeof p.then === "function") {
        p.then((data) => {
          // 异步
          process(data, i);
        }, reject); // 如果有一个promise失败了 就执行最后的失败逻辑
      } else {
        process(p, i); // 同步
      }
    }
  });
};

/**
 * 1.finally 无论成功失败都会执行的方法 并且可以继续调用then
 * data可以穿透传递到下一个then
 * 2. finally里的回调如果返回的是一个promise,会有等待效果 
 * 会等待promise执行完毕,再将上一个结果传到下面的then中去
 * 如果promise失败 会将失败的promise参数传递给下一个then的失败回调中作为参数
*/

Promise.resolve("ok")
.finally(() => {
  // finally里的函数是没有参数的,会把上面的参数传递到下面的then中
  console.log("无论成功失败都会调用")
})
.then((data) => {
  console.log('成功', data);
})
.catch((err) => {
  console.log('失败', err);
})

Promise.resolve("ok")
.finally(() => {
  // finally里的函数是没有参数的,会把上面的参数传递到下面的then中
  console.log("无论成功失败都会调用")
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      resolve("XXXXX");
    }, 1000)
  })
})
.then((data) => {
  console.log('成功', data);
})
.catch((err) => {
  console.log('失败', err);
})

Promise.prototype.finally = function(cb) {
  return this.then((y) => {
    // cb()
    // return y;
    // cb()执行完可能是一个promise,并且有等待效果 不管是不是 统一包装成promise
    // 不用Promise.reject()包裹是因为它不具备等待效果
    // 参数d是“XXXXX” y是“ok”
    // cb执行一旦报错,就直接跳过后续的then的执行逻辑,直接将错误向下传递
    return Promise.resolve(cb()).then(d => y)
  },(r) => {
    // cb();
    // throw r;
    return Promise.resolve(cb()).then(d => { throw r })
  })
}

  • 异步方法转换成promise形式
// 1.可以将所有的原有api变成promise的形式
const fs = require("fs").promises; 

// 2. 单独将某一个方法转换成promise的形式
const fs = require("fs");
const util = require("util"); // node中的另一个基础模块(工具方法)
util.promisify(fs.readFile)

// 使用
let read = util.promisify(fs.readFile);
read("note.md", "utf8").then(data => {
    console.log(data);
})
// 涉及到如何将不是promise的异步api转换成promise

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

function promisifyAll(obj) {
    for(let key in obj) {
        if(typeof obj[key] === 'function') {
            obj[key] = promisify(obj[key]);
        }
    }
    return obj;
}

  • Promise.race
// 使用
Promise.race([fs.readFile("a.md", "utf8"), fs.readFile("b.md","utf8")]).then(data => {
    console.log(data)
}, err => {
    console.log(err);
});

// 实现
Promise.race = function(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{// 不是promise
                resolve(p);
            }
        }
    })
}
  • Promise.race的一个应用:请求超时
// 模拟获取图片的promise
let p = new Promise((resolve, reject)=> {
    setTimeout(() => {
        resolve("图片加载成功")
    }, 3000);
});

function wrap(oldPromise) {
    let abort;
    // 增加一个promise(p2), 可以决定promise.race的成功和失败
    let p2 = new Promise((resolve, reject) => {
        abort = reject;
    })
    let returnedPromise = Promise.race([oldPromise, p2]);
    returnedPromise.abort = abort;
    return returnedPromise
}

let newPromise = wrap(p);
setTimeout(() => {
    newPromise.abort("超时");
}, 2000);
newPromise.then(data => {
    console.log(data)
}, err => {
    console.log(err);
})

如何中断promise的链式调用

  • 怎么让promise不走到下面的then

    方案是:返回一个等待的promise

  • 中断的两种方式
    1. 不采用原有的结果,可以考虑采用Promise.race
    2. 破坏链式调用,返回pending的promise
Promise.resolve("1").then(data => {
   console.log(data);
   return new Promise(()=>{}); //返回一个promise,会采用他的状态,如果不成功也不失败就不会向下执行了
}).then(data => {
   console.log(data);
})

实现异步并发上限控制

class LimitPromise {
   constructor(max) {
       this._max = max; // 异步任务并发上限
       this._count = 0; // 当前正在执行的任务数量
       this._taskQueue = []; // 等待执行的任务队列
   }
   /**
    * call 调用器 将异步任务函数和他的参数传入
    * caller 异步任务函数 必须是async函数或者返回promise函数
    * args 异步任务函数大的参数列表
    * returns 返回一个新的promise
   */
   call(caller, ...args) {
       return new Promise((resolve, reject) => {
           // 创建一个任务 判断任务是执行还是入队
           const task = this._createTask(caller, args, resolve, reject);
           if(this._count >= this._max) {
               this._taskQueue.push(task);
           }else{
               task();
           }
       })
   }
   /**
    * _createTask 创建一个任务
    * caller 实际执行函数
    * args 执行函数的参数
    * 返回一个任务函数
   */
   _createTask(caller, args, resolve, reject) {
       return () => {
           // 当前请求数量+1
           this._count++;
           // 实际上是在这里调用了异步任务,并将异步任务的返回(resolve,reject)抛给了上一层
           caller(...args)
           .then(resolve)
           .catch(reject)
           .finally(() => {
               // 在finally中判断是否执行下一个任务,实现任务队列连续消费的地方就是这里
               // 任务队列的消费区,利用Promise.finally方法在异步任务结束后取出下一个任务执行
               this._count --;
               if(this._taskQueue.length) {
                   let task = this._taskQueue.shift();
                   task()
               }
           })
       }
   }
}

// 使用
// 假设我们有一个网络请求模块,叫request.js,包含get和post方法,一般情况下,是这样使用的:
const request = require('./request')
request.get('https://www.baidu.com')
 .then((res) => {
   // 处理返回结果
 })
 .catch(err => {
   // 处理异常情况
 })
 
 
 
// 现在我们要把它改造成受限制的网络请求,假设请求上限设为10个,并起名叫limitRequest.js。实现如下:
const LimitPromise = require('limit-promise')
const request = require('./request')
// 请求上限
const MAX = 10
// 核心控制器
const limitP = new LimitPromise(MAX)

// 利用核心控制器包装request中的函数
function get (url, params) {
 return limitP.call(request.get, url, params)
}
function post (url, params) {
 return limitP.call(request.post, url, params)
}
// 导出
module.exports = {get, post}