# Promise

时间:2023.12.01

# 定义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

Promise对象有以下两个特点。

  • (1) 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • (2) 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

# 基本用法

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))

上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

调用resolve或reject并不会终结 Promise 的参数函数的执行

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

# Promise方法

  • Promise.prototype.then()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.allSettled()
  • Promise.race(): 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。
  • Promise.any(): 与race的区别是:不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
  • Promise.try(): 不知道或者不想区分,函数是同步函数还是异步操作,但是想用 Promise 来处理它。
  • Promise.resolve()
  • Promise.reject()

# 使用 Promise.try 来代替 Primise.resolve().then(f())


const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next

// 使用 Promise.try
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

# Promise 面试题

  • Promise 的 finally 怎么实现的?
Promise.prototype.finally = function (callback) {
 return this.then(
 (value) => Promise.resolve(callback()).then(() => value), 
 (reason) => Promise.resolve(callback()).then(() => { throw reason })
 );
}
  • 实现 Promise.race 函数
Promise.race = function (promiseList) {
 return new Promise((resolve, reject) => {
  promiseList.foreach(promise => {
   return Promise.resolve(promise()).then(() => {
     resolve()
   })
  })
 })
}
  • 使用Promise实现:限制异步操作的并发个数,并尽可能快的完成全部
class FetchWithLimits {
  constructor(limit) {
    this.tasks = [];
    this.limit = limit;
    this.workingNum = 0;
  }
  addTask(task) {
    this.tasks.push(task)
  }
  start() {
    for(let i = 0; i < limit, i ++) {
      this.doNext();
    }
  }
  doNext() {
    if (this.tasks.length < limit) {
      this.workingNum++;
      this.tasks.shift()().then(() => {
        this.workingNum --;
        this.doNext();
      })
    }
  }
}
// 或者
function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 复制urls
  // 这一步是为了初始化 promises 这个"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下标是为了知道数组中是哪一项最先完成
      return index;
    });
  });
  // 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已经完成的下标
        })
        .then(fastestIndex => { // 获取到已经完成的下标
         // 将"容器"内已经完成的那一项替换
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要继续将这个下标返回,以便下一次变量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化传入
    .then(() => { // 最后三个用.all来调用
      return Promise.all(promises);
    });
}
  • 使用Promise封装一个异步加载图片的方法
function loadImg (url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
            resolve(img)
        };
        img.onerror = () => {
            reject()
        };
    })
}

  • 实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。
const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise () {
  // 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
  console.log("done");
  console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]

答案:

function mergePromise(arr) {
    return new Promise((resolve, reject) => {
        const result = [];
        arr.reduce(function (pCollect, ajax, index) {
            return pCollect.then(ajax).then((value) => {
                result.push(value);
                if (result.length === arr.length) {
                    return resolve(result);
                }
            });
        }, Promise.resolve())
    })
}
  • 使用Promise实现红绿灯交替重复亮
const arr = [{
    { color: 'red', duration: 5000 },
    { color: 'yellow', duration: 2000 },
    { color: 'green', duration: 5000 },
}]

function light(lightItem) {
    const { color, duration } = lightItem;
    return new Promise((resolve) => {
        console.log(color)
        setTimeout(() => {
            resolve();
        }, duration)
    })
}

function start(arr) {
    for (let i of arr) {
        await light(arr[i])
    }
    start();
}

start();
  • 实现 Promise.all
Promise.myAll = function(promiseArr) {
  return new Promise((resolve, reject) => {
    const ans = [];
    let index = 0;
    for (let i = 0; i < promiseArr.length; i++) {
      promiseArr[i]
      .then(res => {
        ans[i] = res;
        index++;
        if (index === promiseArr.length) {
          resolve(ans);
        }
      })
      .catch(err => reject(err));
    }
  })
}
  • 实现 Promise

// 模拟实现Promise
// Promise利用三大手段解决回调地狱:
// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected :
      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
    // 保存this
    const self = this;
    return new Promise((resolve, reject) => {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          // try捕获错误
          try {
            // 模拟微任务
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分两种情况:
              // 1. 回调函数返回值是Promise,执行then操作
              // 2. 如果不是Promise,调用新Promise的resolve函数
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不同点:此时是reject
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {
          setTimeout(() => {
            const result = onFulfilled(self.value);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          });
        } catch(e) {
          reject(e);
        }
      } else if (self.status === REJECTED) {
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // 如果是Promise实例,直接返回
      return value;
    } else {
      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    })
  }
  static all(promiseArr) {
    const len = promiseArr.length;
    const values = new Array(len);
    // 记录已经成功执行的promise个数
    let count = 0;
    return new Promise((resolve, reject) => {
      for (let i = 0; i < len; i++) {
        // Promise.resolve()处理,确保每一个都是promise实例
        Promise.resolve(promiseArr[i]).then(
          val => {
            values[i] = val;
            count++;
            // 如果全部执行完,返回promise的状态就可以改变了
            if (count === len) resolve(values);
          },
          err => reject(err),
        );
      }
    })
  }
  static race(promiseArr) {
    return new Promise((resolve, reject) => {
      promiseArr.forEach(p => {
        Promise.resolve(p).then(
          val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}