promise

98 阅读9分钟

Promise

2)Promise优点

②Promise 与事件对比 和事件相比较, Promise 更适合处理一次性的结果。在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值。 Promise 的这个优点很自然。但是,不能使用 Promise 处理多次触发的事件。链式处理是 Promise 的又一优点,但是事件却不能这样链式处理。 ③Promise 与回调对比 解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。 ④Promise 带来的额外好处是包含了更好的错误处理方式(包含了异常处理),并且写起来很轻松(因为可以重用一些同步的工具,比如 Array.prototype.map() )。

3)Promise缺点

1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。 2、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。 3、当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 4、Promise 真正执行回调的时候,定义 Promise 那部分实际上已经走完了,所以 Promise 的报错堆栈上下文不太友好。

/**
 * 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
 * 2. executor 接受两个参数,分别是 resolve 和 reject
 * 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
 * 4. promise 的状态一旦确认,就不会再改变
 * 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 
 *      和 promise 失败的回调 onRejected
 * 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。
 *      如果promise已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。
 *      如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅)
 * 7. then 的参数 onFulfilled 和 onRejected 可以缺省
 * 8. promise 可以then多次,promise 的then 方法返回一个 promise
 * 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled)
 * 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected)
 * 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功,
 *   就走下一个then的成功,如果失败,就走下一个then的失败
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    // 存放成功/失败的队列
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value) => {
      // 如果value是一个Promise 递归解析
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        this.onFulfilledCallbacks.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject) // 立即执行
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected) {
    // 判断 onFulfilled, onRejected 是否是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    let newPromise = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            // resolvePromise(newPromise, x, resolve, reject)
            // 简单的
             if(x instanceof Promise){
                  x.then(
                      value=> resolve(value),
                      reason=> reject(reason)
                  )
              }else{
                  resolve(x)
              }
          } catch (error) {
            reject(error)
          }

        })
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            // resolvePromise(newPromise, x, resolve, reject)
            if(x instanceof Promise){
                  x.then(
                      value=> resolve(value),
                      reason=> reject(reason)
                  )
              }else{
                  resolve(x)
              }
          } catch (error) {
            reject(error)
          }
        })

      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push((() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              // resolvePromise(newPromise, x, resolve, reject)
              if(x instanceof Promise){
                  x.then(
                      value=> resolve(value),
                      reason=> reject(reason)
                  )
              }else{
                  resolve(x)
              }
            } catch (error) {
              reject(error)
            }
          })
        }))
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              // resolvePromise(newPromise, x, resolve, reject)
               if(x instanceof Promise){
                  x.then(
                      value=> resolve(value),
                      reason=> reject(reason)
                  )
              }else{
                  resolve(x)
              }
            } catch (error) {
              reject(error)
            }
          })
        })
      }
    })
    return newPromise
  }
  /**
   * Promise.catch
   * @description  用于promise方法链示,捕获前面 onFulfilled/onRejected 抛出的异常
   * @param {*} errorCallback 
   */
  catch(errorCallback) {
    return this.then(null, errorCallback)
  }
  /**
   * Promise.finally
   * @description finally 传入的函数 无论成功和失败都执行
   * @param {*} callback 回调函数
   * @returns 返回成功/失败
   */
  finally(callback) {
    return this.then((value) => {
      // 返回上一次的值
      return new Promise(callback()).then(() => value)
    }, error => {
      return new Promise(callback()).then(() => { throw error })
    })
  }
  /**
   * Promise.all
   * @description 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve 当有一个promise对象变为reject状态时 直接 reject
   * @param {*} values promise对象组成的数组作为参数
   * @returns 返回一个promise实例
   */
  static all(values) {
    return new Promise((resolve, reject) => {
      let resultArr = []
      let count = 0
      values.forEach((promise, index) => {
        promise.then((value) => {
            resultArr[index] = value
            if (++count === values.length) {
              resolve(resultArr)
            }
        }, reject)
      })
    })
  }
  /**
   * Promise.race
   * @description 只要有一个promise对象进入FULFILLED 或者  REJECTED 状态的话,就会继续执行后面的处理
   * @param {*} values  接受promise对象组成的数组作为参数
   * @returns 返回一个Promise实例
   */
  static race(values) {
    return new Promise((resolve, reject) => {
      values.forEach((promise) => {
        promise.then(resolve, reject)
      })
    })
  }
  // 默认产生一个成功的promise
  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }
  // 默认产生一个失败的promise
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }
}

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


const resolvePromise = (promise, x, resolve, reject) => {
  if (x === promise) {
    // If promise and x refer to the same object, reject promise with a TypeError as the reason.
    reject(new TypeError('循环引用'))
  }
  // if x is an object or function,
  if (x !== null && typeof x === 'object' || typeof x === 'function') {
    // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    let called
    try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
      let then = x.then // Let then be x.then
      // If then is a function, call it with x as this
      if (typeof then === 'function') {
        // If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
        // If/when rejectPromise is called with a reason r, reject promise with r.
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise, y, resolve, reject)
        }, r => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        // If then is not a function, fulfill promise with x.
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // If x is not an object or function, fulfill promise with x
    resolve(x)
  }
}

promise 超时


    // 封装一个严格任务函数,第一个参数为promise对象,第二个参数为判定的超时标准,默认3s
    function strictTack(promise,delay = 3){
      // 函数返回一个超时基准promise对象
      let promiseTimeout = function(delay){
        return new Promise((res,rej)=>{
          setTimeout(()=>{
            rej(new Error("运行超时!"))
          },1000 * delay)
        })
      }
      // race,参数数组内的promise并发执行,一旦其中有一个promise对象产生判决就会终止其余的promise对象
      return Promise.race([promise,promiseTimeout(delay)])
    }
    // 异步任务p1
    let p1 = new Promise((res,rej)=>{
      setTimeout(()=>{
        res("p1 was resoved")
      },1000 * 2)
    })

    // 异步任务p2
    let p2 = new Promise((res,rej)=>{
      setTimeout(()=>{
        res("p2 was resoved")
      },1000 * 4)
    })

    strictTack(p1)
    .then(e=>{
      console.log(e)// p1 was resoved
    }).catch(err=>{
      console.log(err)
    }) 

    strictTack(p2)
    .then(e=>{
      console.log(e)
    }).catch(err=>{
      console.log(err) // Error:运行超时!
    }) 

取消一个promise

function wrap(p) {
    let obj = {};
    let p1 = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject = reject;
    });
    obj.promise = Promise.race([p1, p]);
    return obj;
}

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(123);
    }, 2000);
});
let obj2 = wrap(promise);
obj2.promise.then(res => {
    console.log(res);
});
 // obj2.resolve("请求被拦截了");
 // 在2秒内主动调用obj2.resolve,那么obj2.promise方法就会被替换成我们自己的方法,

isPromise

    export function isPromise(value) {
        return value && typeof value.subscribe !== 'function' && typeof value.then === 'function';
    }

promiseAll和allSeleted

该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

    Promise.all = function(promises) {
        const values = []
        let count = 0
        return new Promise((resolve, reject) => {
            promises.forEach((promise, index) => {
                Promise.resolve(promise).then(res => {
                    count++
                    values[index] = res
                    if (count === promises.length) {
                        resolve(values)
                    }
                }, err => {
                    reject(err)
                })
            })
        })
    }

    Promise.allSeleted = function(promises) {
        let count = 0
        let result = []
        return new Promise((resolve, reject) => {
            promises.forEach((promise, index) => {
                Promise.resolve(promise).then(res => {
                    result[index] = {
                        value: res,
                        reason: null,
                    }
                }, err => {
                    result[index] = {
                        value: null,
                        reason: err,
                    }
                }).finally(() => {
                    count++
                    if (count === promises.length) {
                        resolve(result)
                    }
                })
            })
        })
    }

Promise.race


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

一个可取消的 Promise.race,关键在于:

  • 能在其中一个 Promise 先完成后,取消其他尚未完成的 Promise。
  • 这在原生 JS 中要靠「AbortController」+「自定义逻辑」组合实现。
function cancelableRace(promises) {
  const controllers = promises.map(() => new AbortController());

  // 每个 promise 包装一层,加入中止控制
  const wrapped = promises.map((p, index) => {
    const controller = controllers[index];
    const signal = controller.signal;

    return new Promise((resolve, reject) => {
      // 如果被取消,reject
      signal.addEventListener("abort", () => {
        reject(new Error("Aborted"));
      });

      // 继续原 promise 逻辑
      p.then(resolve).catch(reject);
    });
  });

  let canceled = false;

  const cancel = () => {
    if (canceled) return;
    canceled = true;
    controllers.forEach((c) => c.abort());
  };

  // 第一个 resolve/reject 后取消其他的
  const race = Promise.race(wrapped).finally(cancel);

  return { promise: race, cancel };
}

function delay(ms, value, shouldReject = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      shouldReject ? reject(value) : resolve(value);
    }, ms);
  });
}

const p1 = delay(1000, "one");
const p2 = delay(500, "two");
const p3 = delay(2000, "three");

const { promise, cancel } = cancelableRace([p1, p2, p3]);

promise
  .then((res) => console.log("✅ Winner:", res))
  .catch((err) => console.log("❌ Error:", err.message));

Promise.allSeleted 加缓存

    const machTemList = [
      () => require.async("../../../../packages/mach-common-template/index.js"),
      () => require.async("../../../../packages/mach-user-template/index.js"),
      () => require.async("../../../../packages/mach-mactivity-template/index.js"),
    ];
    // mach模版分包加载状态上报
    const machTemOwl = {};
    // mach模版分包缓存
    const machCache = new Map();
    const handlePromise = (_req, _cache, i) => {
  const _promise = Promise.resolve(_req());
  _cache.set(i, () => _promise);
  return _promise.then(res => {
    _cache.set(i, () => res);
    return {
      status: "ful",
      value: res
    }
  }).catch((e) => {
    _cache.delete(i);
    return ({
      status: 'rej',
      reason: e
    })
  })
};
const getMachTem = function (machTem, key) {
  return Promise.all(machTem.map((function (req, i) {
    if(!machCache.has(i)) {
      return handlePromise(req, machCache, i);
    } else {
      return Promise.resolve(machCache.get(i)()).then(res => {
        return {
          status: "ful",
          value: res
        }
      }).catch((e) => {
        machCache.delete(i);
        return handlePromise(req, machCache, i);
      })
    }
  }))).then( (res) => {
    try {
      if (!machTemOwl[key]) {
        // 只上报一次
        machTemOwl[key] = true;
        owlInstance.setMetric(key, 1, {
          status: (res || []).map((_t) => (_t && _t.status || 'fulrej')).join(','),
          version: VERSION,
          keys: (res || []).map((_t) => (_t && _t.value ? Object.keys(isObj(_t.value) ? _t.value : {}).length : '-1')).join(','),
        });
      }
    } catch (e) {}
    return res.reduce((pre, cur) => cur.status === 'ful' ? Object.assign({}, pre, cur.value) : pre, {});
  })
};

实现maxRequest,成功后resolve结果,失败后重试,尝试超过一定次数才真正的reject

function maxRequest(fn, maxNum) {
    return new Promise((resolve, reject) => {
        if (maxNum === 0) {
            reject('max request number')
            return
        }
        Promise.resolve(fn()).then(value => {
            resolve(value)
        }).catch(() => {
            return maxRequest(fn, maxNum - 1)
        })
    })
}

红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)

    function red() {
                console.log('red');
            }
            function green() {
                console.log('green');
            }
            function yellow() {
                console.log('yellow');
            }

            function light(cb, timer) {
                return new Promise(resolve => {
                    setTimeout(() => {
                        cb();
                        resolve()
                    }, timer);
                })
            }

            function step() {
                Promise.resolve().then(() => {
                    return light(red, 3000)
                }).then(() => {
                    return light(green, 2000)
                }).then(() => {
                    return light(yellow, 1000)
                }).finally(() => {
                    return step()
                })
            }

先并发请求 3 张图片,当一张图片加载完成后,又会继续发起一张图片的请求,让并发数保持在 3 个,直到需要加载的图片都全部发起请求。

    var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/10/29/166be40ccc434be0~tplv-t2oaga2asx-image.image'];
    function loadImg(url) {
        return new Promise((resolve, reject) => {
            const img = new Image()
            img.onload = function () {
                console.log('一张图片加载完成');
                resolve();
            }
            img.onerror = reject
            img.src = url
        })
    };

    function limitLoad(urls, handler, limit) {
        // 对数组做一个拷贝
        const sequence = [].concat(urls)
        let promises = [];

        //并发请求到最大数
        promises = sequence.splice(0, limit).map((url, index) => {
            // 这里返回的 index 是任务在 promises 的脚标,用于在 Promise.race 之后找到完成的任务脚标
            return handler(url).then(() => {
                return index
            }); 
        });

        // 利用数组的 reduce 方法来以队列的形式执行
        return sequence.reduce((last, url, currentIndex) => {
            return last.then(() => {
                // 返回最快改变状态的 Promise
                return Promise.race(promises)
            }).catch(err => {
                // 这里的 catch 不仅用来捕获 前面 then 方法抛出的错误
                // 更重要的是防止中断整个链式调用
                console.error(err)
            }).then((res) => {
                // 用新的 Promise 替换掉最快改变状态的 Promise
                promises[res] = handler(sequence[currentIndex]).then(() => { return res });
            })
        }, Promise.resolve()).then(() => {
            return Promise.all(promises)
        })

    }
    limitLoad(urls, loadImg, 3)

    /*因为 limitLoad 函数也返回一个 Promise,所以当 所有图片加载完成后,可以继续链式调用limitLoad(urls, loadImg, 3).then(() => {    console.log('所有图片加载完成');}).catch(err => {    console.error(err);})*/

并发请求控制

function loadImagesWithConcurrency(urls, concurrency = 3) {
  let index = 0; // 当前待请求的URL索引
  const total = urls.length;
  let completed = 0; // 已完成的请求数

  // 发起单个请求
  function fetchNext() {
    if (index >= total) return; // 所有URL都已发起请求

    const url = urls[index++];
    console.log(`发起请求: ${url}`);

    fetch(url)
      .then(response => {
        if (!response.ok) throw new Error(`请求失败: ${url}`);
        console.log(`加载成功: ${url}`);
      })
      .catch(error => {
        console.error(`加载失败: ${error.message}`);
      })
      .finally(() => {
        completed++;
        // 每完成一个请求,就再发起一个新的,维持并发数
        fetchNext();
        // 所有请求都完成时的回调
        if (completed === total) {
          console.log("所有图片请求已完成");
        }
      });
  }

  // 初始启动并发请求
  for (let i = 0; i < concurrency && i < total; i++) {
    fetchNext();
  }
}

// 示例用法
const imageUrls = [
  "url1.jpg", "url2.jpg", "url3.jpg", "url4.jpg", "url5.jpg", "url6.jpg"
];
loadImagesWithConcurrency(imageUrls, 3);