promise

2019/11/08 javascript

从0到1搞一哈promise

1、发布订阅 on-emit

发布订阅模式需要有一个消息管理器,发布者并不会直接去联系订阅者,他们之间有一个中间人来处理消息队列(函数)。 发布者和订阅者是解耦的。

  // 发布订阅
  let e = {
    arr: [],
    on(fn){
      this.arr.push(fn);
    },
    emit(){
      this.arr.forEach((fn) => fn());
    }
  }

  // 订阅
  e.on( ()=> {
    console.log('1');
  })

  // 发布
  function something (){
    e.emit();
  }

  something();

2、观察者模式 observer

发布订阅是观察者模式的一部分,观察者模式分两个角色,观察者和被观察者,被观察者状态发生改变之后通知自己身上的所有观察者更新状态。观察者和被观察者是有联系的。

  class Student {  // 被观察者
    constructor(){
      this.teachers = []; // 存放订阅/观察者
      this.state = '认真学习'; // 状态
    }
    attach(t){
      this.teachers.push(t);  // 订阅
    }
    setState(newState){
      this.state = newState;  // 修改被观察者的状态
      this.teachers.forEach((t) => t.update(newState)) // 被观察者的状态改变后通知观察者
    }
  }

  class Teacher {
    constructor(name){
      this.name = name
    }
    update(newState){
      console.log(this.name + '的学生' + newState + '了,要做某事了');
    }
  }


  let s = new Student();
  let t1 = new Teacher('张老师');
  let t2 = new Teacher('李老师');

  // 订阅, 把观察者放到被观察者身上去
  s.attach(t1);
  s.attach(t2);

  s.setState('谈恋爱');

  // 张老师的学生谈恋爱了,要做某事了
  // 李老师的学生谈恋爱了,要做某事了

3、promise

3.1 promise的作用

  1. 解决并发问题:同步获取多个异步的执行结果。

  2. 链式调用问题(多个回调嵌套):下一个接口的参数依赖于上一个接口的返回值

3.2 promise的使用

  1. promise是一个类

  2. 每次new promise都需要传一个执行器函数,这个函数是立即实行的

  3. 执行器函数有两个参数 resolve(成功) reject(失败)

  4. promise默认有三个状态, pending(等待) resolve(成功) reject(失败)

  5. 状态一旦改变,不能再次改变,改变会被忽略。

  6. 每个promise都有then方法,还可以链式调用.then().then()多次。

  7. promise实例可以调用then方法去监控成功或者失败的回调(resolve or reject), 第一个参数是成功的回调,第二个参数是失败的回调。

  let p = new Promise((resolve, reject) => {
    // 只能是成功或者失败之一,因为状态一旦改变,不能再次改变。
    resolve('成功了');
    // reject('失败了');
  })

  p.then((data) => {
    console.log(data);
  }, (err) => {
    console.log(err);
  })

3.3 简易版promise实现

根据上面的规则我们可以写出来一个简易版的promise,不处理异步情况,不处理链式调用。

  // promise 的三个状态
  const PENDING = 'PENDING';
  const FULFILLED = 'FULFILLED';
  const REJECTED = 'REJECTED';

  class Promise {
    constructor(executor) {
      // 定义成功或者失败的值
      this.value = undefined;
      this.reason = undefined;
      // 定义状态
      this.status = PENDING;

      let resolve = (value)=>{
        // 只有当状态为PENDING的时候才可以修改状态
        if(this.status === PENDING){
          this.value = value;
          this.status = FULFILLED;
        }
      }

      let reject = (reason)=>{
        // 只有当状态为PENDING的时候才可以修改状态
        if(this.status === PENDING){
          this.reason = reason;
          this.status = REJECTED;
        }
      }
      // 每个promise有两个参数,成功or失败,都为函数
      // 这里可能会发生异常
      try {
        executor(resolve, reject);
      } catch (error) {
        reject(error);
      }
    }
    // .then 接受两个参数,一个成功的回调,一个失败的回调。判断当前的状态 如果状态为成功则调用成功的回调参数,否则调失败的
    then(onFulfilled, onRejected){
      if(this.status === FULFILLED){
        onFulfilled(this.value);
      }
      if(this.status === REJECTED){
        onRejected(this.reason);
      }
    }
  }

  // 调用
  let p = new Promise((res, rej)=>{
    res('hi')
  })

  p.then(data => {
    console.log(data)
  }, err=>{
    console.log(err)
  })

3.4 处理异步的promise

使用发布订阅模式处理异步请求,不处理链式调用

  // promise 的三个状态
  const PENDING = 'PENDING';
  const FULFILLED = 'FULFILLED';
  const REJECTED = 'REJECTED';

  class Promise {
    constructor(executor) {
      // 定义成功或者失败的值
      this.value = undefined;
      this.reason = undefined;
      // 定义状态
      this.status = PENDING;

      // 使用发布订阅模式来做异步处理,先定义成功和失败的序列
      this.onResolvedCallbacks = [];
      this.onRejectedCallbacks = [];

      let resolve = (value)=>{
        // 只有当状态为PENDING的时候才可以修改状态
        if(this.status === PENDING){
          this.value = value;
          this.status = FULFILLED;
          this.onResolvedCallbacks.forEach(fn => fn());
        }
      }

      let reject = (reason)=>{
        // 只有当状态为PENDING的时候才可以修改状态
        if(this.status === PENDING){
          this.reason = reason;
          this.status = REJECTED;
          // 可能会有之前存起来的订阅(回调),把他发布了(执行)
          this.onRejectedCallbacks.forEach(fn => fn());
        }
      }
      // 每个promise有两个参数,成功or失败,都为函数
      // 这里可能会发生异常
      try {
        executor(resolve, reject);
      } catch (error) {
        reject(error);
      }
    }
    // .then 接受两个参数,一个成功的回调,一个失败的回调。判断当前的状态 如果状态为成功则调用成功的回调参数,否则调失败的
    then(onFulfilled, onRejected){
      if(this.status === FULFILLED){
        onFulfilled(this.value);
      }
      if(this.status === REJECTED){
        onRejected(this.reason);
      }
      // 如果异步调用了,这个时候promise的状态是pending
      if(this.status === PENDING){
        this.onResolvedCallbacks.push(()=>{
          onFulfilled(this.value);
        })
        this.onRejectedCallbacks.push(()=>{
          onRejected(this.reason);
        })
      }
    }
  }

3.5 完整的promise

promise链式调用:p().then().then()....;

每一次.then 如果返回的是普通值(普通值:不是promise也不是错误)就会继续走下一个.then()的成功,如果有返回值,则当前的返回值是下一个then的参数,没有的话为undefind

如果返回的是一个promise,那么这个promise会立即执行并且采用该promise的状态,把结果传给外层的.then()里面,如果是成功就走成功,失败就走失败。

如果抛出了错误,自己有错误的回调监听走自己的,没有则走下一个.then的失败。

每个then返回一个新的promise

所以如果想让下一个thenerror的话,需要返回一个失败的promise 或 抛出异常

  // promise 的三个状态
  const PENDING = 'PENDING';
  const FULFILLED = 'FULFILLED';
  const REJECTED = 'REJECTED';

  // promise处理函数
  const resolvePromise = (promise2, x, resolve, reject) => {
    // 
    if(promise2 === x){ // 如果引用相等,则说明是同一个对象,抛出错误
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
    }
    // 如果当前x是对象,但是不为null  或 x 是function的话
    if((typeof x === 'object' && x !== null) || typeof x === 'function'){
      let called = false;
      try {
        let then = x.then;
        if(typeof then === 'function'){
          then.call(x, y=>{
            if(called) return;  // 防止多次调用
            called = true;
            resolvePromise(promise2, y, resolve, reject); 
          }, r=>{
            if(called) return;
            called = true;
            reject(r);
          });
        } else {
          resolve(x);
        }
      } catch (e) {
        if(called) return;
        called = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  }

  class Promise {
    constructor(executor) {
      // 定义成功或者失败的值
      this.value = undefined;
      this.reason = undefined;
      // 定义状态
      this.status = PENDING;

      // 使用发布订阅模式来做异步处理,先定义成功和失败的序列
      this.onResolvedCallbacks = [];
      this.onRejectedCallbacks = [];

      // 如果一个promise resolve一个新的promise,会等到这个内部的promise执行完成,reject会直接失败
      if(value instanceof Promise){
        return value.then(resolve, reject);
      }

      let resolve = (value) => {
        // 只有当状态为PENDING的时候才可以修改状态
        if (this.status === PENDING) {
          this.value = value;
          this.status = FULFILLED;
          this.onResolvedCallbacks.forEach(fn => fn());
        }
      };

      let reject = (reason) => {
        // 只有当状态为PENDING的时候才可以修改状态
        if (this.status === PENDING) {
          this.reason = reason;
          this.status = REJECTED;
          // 可能会有之前存起来的订阅(回调),把他发布了(执行)
          this.onRejectedCallbacks.forEach(fn => fn());
        }
      };
      // 每个promise有两个参数,成功or失败,都为函数
      // 这里可能会发生异常
      try {
        executor(resolve, reject);
      } catch (e) {
        reject(e);
      }
    }
    // .then 接受两个参数,一个成功的回调,一个失败的回调。判断当前的状态 如果状态为成功则调用成功的回调参数,否则调失败的
    then(onFulfilled, onRejected) {
      // 选参
      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
      onRejected = typeof onRejected === 'function' ? onRejected : error => {throw error};
      
      // then方法调用后应该返回一个新的promise
      // 每次new promise的时候,promise的控制器立即执行
      let promise2 = new Promise((resolve, reject) => {
        // 把当前的值传递给下一个promise里面 
        if (this.status === FULFILLED) {
          // 当前promise2还没有值,所以需要异步一下获取
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e)
            }
          })
        }
        if (this.status === REJECTED) {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e)
            }
          })
        }
        // 如果异步调用了,这个时候promise的状态是pending
        if (this.status === PENDING) {
          this.onResolvedCallbacks.push(() => {
            setTimeout(() => {
              try {
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e)
              }
            })
          })
          this.onRejectedCallbacks.push(() => {
            setTimeout(() => {
              try {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e)
              }
            })
          })
        }
      })
      return promise2;
    }
    catch(errCallback){ // 没有成功的then
      return this.then(null,errCallback)
    }
    static reject(reason){ // 创建了一个失败的promise
      return new Promise((resolve,reject)=>{
        reject(reason);
      })
    }
    static resolve(value){ // 创建了一个成功的promise
      return new Promise((resolve,reject)=>{
        resolve(value);
      })
    }
  }

4、promise.all()

全部完成才算完成,如果有一个失败就失败,用来处理多个异步并发问题。

promise.all是按照顺序执行的。

  Promise.all = function(promises) {
    return new Promise((resolve, reject)=>{
      let arr = []; //存放最后的结果
      let index = 0;
      let processData = (i, data)=>{
        arr[i] = data;
        if(++index === promises.length){
          resolve(arr);
        }
      }
      for(let i = 0; i<promises.length; i++){
        let current = promises[i];
        if((current.then && typeof current.then === 'object') || typeof current.then === 'function'){
          current.then((data)=>{
            processData(i, data);
          }, reject)
        } else {
          processData(i, current);
        }
      }
    })
  }

  let p1 = new Promise(function (resolve, reject) {
    resolve(1);
  })
  let p2 = new Promise(function (resolve, reject) {
    resolve(2);
  })
  let p3 = new Promise(function (resolve, reject) {
    resolve(3);
  })

  let p4 = Promise.all([p1, p2, p3]);
  p4.then(function (value) {
    console.log(value);     // [1,2,3];
  })

原理:

  1. promise.all()返回一个新的promise
  2. promise.all()等待所有异步方法都完成才返回,如果有一个失败就失败
  3. 根据上面的情况,我们需要一个计数器index,一个数据的收集数组arr
  4. 如果计数器的数量等于参数的数量则返回arr
  5. 如果某一项为普通值的话根据promise A+走resolve
  6. 如果某一项执行失败,则直接返回reject

5、promise.race();

要求传入的值为一个可序列化的值,我这里就简单要求就是一个数组。

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 – MDN

  Promise.race = function(promises) {
    return new Promise((resolve, reject)=>{
      if(!Array.isArray(promises)){
        throw new TypeError('argument should be an array')
      }
      for(let i = 0; i<promises.length; i++){
        let current = promises[i];
        if((current.then && typeof current.then === 'object') || typeof current.then === 'function'){
          current.then(resolve, reject)
        } else {
          resolve(current)
        }
      }
    })
  }

  let p1 = new Promise(function (resolve, reject) {
    setTimeout(()=>{
      resolve(1);
    },200)
  })
  let p2 = new Promise(function (resolve, reject) {
    resolve(2);
  })
  let p3 = new Promise(function (resolve, reject) {
    setTimeout(()=>{
      resolve(3);
    },500)
  })

  let p4 = Promise.race([p1, p2, p3]);
  p4.then(function (value) {
    console.log(value);     // 2
  }) 

原理:

  1. 最先执行完毕的那个是成功就返回成功,失败就返回失败。
  2. 先判断是不是普通值,如果是直接成功。
  3. 如果不是普通值,去执行,哪个先执行完就返回哪个。

6、promise.finally()

该方法不管promise成功还是失败都会执行。

Promise.resolve()会等待里面的promise完成后执行完成。

  Promise.prototype.finally= callback => {
    return this.then((val)=>{
      // 等待finally里面的函数执行完毕之后执行, finally可能返回一个promise
      return Promise.resolve(callback()).then(() => val);
    }, (err)=>{
      return Promise.resolve(callback()).then(() => {throw err});
    })
  }

7、promise.try()

捕获错误,不论是同步还是异步。

  Promise.try= callback => {
    return new Promise((resolve, reject)=>{
      return promise.resolve(callback()).then(resolve, reject);
    })
  }

Search

    Table of Contents