异步专题

209 阅读9分钟

一、Promise

1、谈谈你了解的Promise?

Promise 对象是一个代理对象。它接受你传入的 executor(执行器)作为入参,允许你把异步任务的成功和失败分别绑定到对应的处理方法上去。一个 Promise 实例有三种状态:

• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态;
• fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
• rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;

Promise实例的状态是可以改变的,但它只允许被改变一次。当我们的实例状态从 pending 切换为 rejected 后,就无法再扭转为 fulfilled,反之同理。当 Promise 的状态为 resolved 时,会触发其对应的 then 方法入参里的 onfulfilled 函数;当 Promise 的状态为 rejected 时,会触发其对应的 then 方法入参里的 onrejected 函数。

// 手写Promise

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
  constructor(executor){
    // executor 是一个执行器,进入会立即执行
    // 并传入resolve和reject方法
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING;
  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;

  // 存储成功回调函数
  onFulfilledCallbacks = [];
  // 存储失败回调函数
  onRejectedCallbacks = [];

  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
      // resolve里面将所有成功的回调拿出来执行
      while (this.onFulfilledCallbacks.length) {
        // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
        this.onFulfilledCallbacks.shift()(value)
      }
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
      // resolve里面将所有失败的回调拿出来执行
      while (this.onRejectedCallbacks.length) {
        this.onRejectedCallbacks.shift()(reason)
      }
    }
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = realOnFulfilled(this.value);
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        })  
      }

      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = realOnRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        }) 
      }
      // 判断状态
      if (this.status === FULFILLED) {
        fulfilledMicrotask() 
      } else if (this.status === REJECTED) { 
        rejectedMicrotask()
      } else if (this.status === PENDING) {
        // 等待
        // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
        // 等到执行成功失败函数的时候再传递
        this.onFulfilledCallbacks.push(fulfilledMicrotask);
        this.onRejectedCallbacks.push(rejectedMicrotask);
      }
    }) 
    
    return promise2;
  }

  // resolve 静态方法
  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter;
    }

    // 转成常规方式
    return new MyPromise(resolve =>  {
      resolve(parameter);
    });
  }

  // reject 静态方法
  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #'))
  }
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

2、Promise的方法有哪些?

(1)Promise.all() 方法接受一个数组作为参数, p1 、 p2 、 p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外, Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p 的状态由 p1 、 p2 、 p3 决定,分成两种情况。

(1)只有 p1 、 p2 、 p3 的状态都变成 fulfilled , p 的状态才会变成 fulfilled ,此时 p1 、 p2 、 p3 的返回值组成一个数组,传递给 p 的回调函数。

(2)只要 p1 、 p2 、 p3 之中有一个被 rejected , p 的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

//手写Promise.all
Promise._all = function (promises) {
    return new Promise((resolve, reject) => {
    const results =[];
    for (let i = 0;i< promises.length;i++) {
        const promise=promises[i];
       // 这里使用 Promise.resolve 包了一下,以防传递了 non-promise        Promise.resolve(promise).then(res => {
            results.push(res);
            if (results.length=== promises.length) resolve(results);
        }).catch(reject);
      }
    })
}

Promise._all([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

(2)Promise.race()和Promise.all()类似,不同之处是只要 p1 、 p2 、 p3 之中有一个实例率先改变状态, p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

//手写Promise.race
Promise._race =function (promises) {
return new Promise((resolve, reject) => {
  promises.forEach(promise => {
    Promise.resolve(promise).then(res => {
      resolve(res);
    }, reson =>{
      reject(reson);
    })
  })
});
Promise._race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(res=>{
  console.log(res);
},res=>{
  console.log(res);
});

(3)Promise.allSettled() 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是 fulfilled 还是 rejected ,包装实例才会结束。该方法由 ES2020 引入。

该方法返回的新的 Promise 实例,一旦结束,状态总是 fulfilled ,不会变成 rejected 。它的监听函数接收到的参数是数组 results 。该数组的每个成员都是一个对象,对应传入 Promise.allSettled() 的两个 Promise 实例。每个对象都有 status 属性,该属性的值只可能是字符串 fulfilled 或字符串 rejected 。 fulfilled 时,对象有 value 属性, rejected 时有 reason 属性,对应两种状态的返回值。

Promise.allSettled = function (promises) {
    return new Promise(resolve => {
      const data = [], len = promises.length;
      let count = len;
      for (let i = 0; i < len; i += 1) {
        const promise = promises[i];
        promise.then(res => {
          data[i] = { status: 'fulfilled', value: res };
        }, error => {
          data[i] = { status: 'rejected', reason: error };
        }).finally(() => { // promise has been settled
          if (!--count) {
            resolve(data);
          }
        });
      }
    });
  }
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
//在all的基础上写allSettled
Promise.allSettled = function (promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(res => {
      return { status: 'fulfilled', value: res }
    }, error => {
      return { status: 'rejected', reason: error }
    })));
  };

(4)Promise.any()

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。该方法目前是一个第三阶段的提案 。

原型上的方法finally

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
//使用
var promise = new Promise(function(resolve, reject) {
    console.log("promise")
    window.setTimeout(function(){
      if (false){
        resolve('huangbiao');
      } else {
        debugger
        reject('error');
      }
    },1000)
  }).then(function(){
    console.log('success');
  }).catch(function(){
    console.log('catch');
  }).finally(function(){
    console.log('finally');
  });

3、Promise真题

//promise的状态一旦变为成功或失败就不会再次改变

const promise = new Promise((resolve, reject) => {
  resolve('第 1 次 resolve')
  console.log('resolve后的普通逻辑')
  reject('error')
  resolve('第 2 次 resolve')
})
 
promise
.then((res) => {
  console.log('then: ', res)
})
.catch((err) => {
  console.log('catch: ', err)
})

输出:resolve后的普通逻辑
then:  第 1 次 resolve

//Promise的值穿透,then的参数为函数,如果不是函数则会被当做透明
Promise.resolve(1)
  .then(Promise.resolve(2))
  .then(3)
  .then()
  .then(console.log)

输出:1

//
setTimeout(() => {
console.log(1);
},0);
new Promise(function(resolve){
resolve();
console.log(2);
}).then(console.log(3))
console.log(4);

// 2 3 4 1
then里传入了一个已调用的函数,而不是函数的引用,所以会被立即执行掉

二、Gernerator简介

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中

有一种叫做“协程”(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程,它的运行流程大致如下:

第一步,协程 A 开始执行;

第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B;

第三步,(一段时间后)协程 B 交还执行权;

第四步,协程 A 恢复执行;

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下:

function* asyncJob() { // ... var f = yield readFile(fileA); // ...}

上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示

执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的

分界线。协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往

后执行。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即

暂停执行)。

function* gen(x) {var y = yield x + 2;return y;
}var g = gen(1);g.next() // { value: 3, done: false }g.next(2) // { value: 2, done: false }

next 是返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以

接受参数,向 Generator 函数体内输入数据。

上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值 3。第二

个 next 方法带有参数 2,这个参数可以传入 Generator 函数,作为 上个阶段 异步

任务的返回结果,被函数体内的变量 y 接收。因此,这一步的

value 属性,返回

的就是 2(变量 y 的值)。

二、async/await

async 函数是Generator和Promise的语法糖。对 Generator 函数的改进,体现在以下四点:

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了 co 模块,而 async 函数自带执行器。也就是说, async 函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了 asyncReadFile 函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。

(2)更好的语义。

async 和 await ,比起星号和 yield ,语义更清楚了。 async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co 模块约定, yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。

进一步说, async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。

错误捕获

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法
async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

用for...of遍历可以继发执行

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  for (let doc of docs) {
    await db.post(doc);
  }
}
//或reduce
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  await docs.reduce(async (_, doc) => {
    await _;
    await db.post(doc);
  }, undefined);
}

//foreach遍历出来的会是并发执行
function dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];
  // 可能得到错误结果,因为这里会并发执行
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

实现原理

async function fn(args) {
  // ...
}
// 等同于
function fn(args) {
  return spawn(function* () {
    // ...
  });
}

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

一道经典面试题:

//输出结果
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');