promise与异步编程

我们都知道,没有promise的时代,异步的实现是通过回调来实现的。造成的结果就是回调地狱,书写代码的形式就是一层嵌套一层,看起来非常不直观。
后来ES5出来了promise的解决方案,书写代码以链式的方式来调用。我们看2个简单的例子。

let promise = readFile('example.txt')
promise.then(
  function(contents){
        console.log(contents)
    },
  function (err) {
    console.log(err)
})

生成器

由于promise的书写方式还是不够直观,所有ES6出来了生成器的概念。那什么是生成器呢?

生成器是一种返回迭代器的函数,通过function 关键字后的星号(*)来表示,函数中会用到新的关键字yield,星号可以紧挨着funcition关键字,也可以在中间添加一个空格。

function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
// 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
let itor = createIterator()
console.log(itor.next().value) 1
console.log(itor.next().value) 2
console.log(itor.next().value) 3
生成器的定义中包含了迭代器,那我们有必要学习一下什么是迭代器:

迭代器是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象,结果对象有2个属性,{value: "", done: true|false}.迭代器还会保存一个内部指针,用来指向当前集合中的位置,每次调用next().都会返回写一个可用的值。

我们使用ES5 语法创建一个迭代器:

function createIterator(items){
  var i = 0;
  return {
    next: function () {
        var done = (i >= items.length)
      var value = !done ? items[i++] : undefined
      return {
        done: done,
        value: value
      }
    }
  }
} 
var iterator = createIterator([1,2,3])
iterator.next() {value: 1, done: false}
iterator.next() {value: 2, done: false}
iterator.next() {value: 3, done: false}
iterator.next() {value: undefine, done: true}
iterator.next() {value: undefine, done: true}
// 之后所有的调用都返回相同的内容。

通过以上的分析,我们解释了什么是生成器迭代器, 而且明确了他们之间的关系,下面我是看看使用生成器如何实现异步操作:

生成器实现异步方案
function* fun() {
    //myAjax返回promise,执行异步操作
      let a = yield myAjax('...') 
      let b = yield myAjax('...') 
      yield myAjax('...')  
  }
  
  // 生成迭代器f, 执行到第一个yield停止,yield后的语句不会执行
  var f = fun() 
  // 执行到第一个yield的语句
  var g = f.next()
  // 第一个data是第一个yield后面的代码执行后返回的异步结果。
  g.value.then(function (data1) {
    // 通过这一句代码,可以把data1赋值与a
    var g = f.next(data1)
    g.value.then(function(data2){
        // 通过这一句代码,可以data2赋值与b
        f.next(data2)
    })
  }, 
  function () {})
 思考: 如果可以让以上过程自动执行,不就可以实现异步操作了吗,并且书写代码的方式也不用存在嵌套了。

封装生成器自动执行next

function* fun() {
    //myAjax返回promise,执行异步操作
      let a = yield myAjax('...') 
      let b = yield myAjax('...') 
      yield myAjax('...')  
 }
var f = fun()
function handle(res){
    if (res.done) return 
    res.value.then(function (data) {
        // 递归调用
        handle(f.next(data))
    })
}
handle(f.next())

基于以上的原理,JS社区出现了一个大牛 TJ Holowaychuk,写了著名的co函数。

function* fun() {
    //myAjax返回promise,执行异步操作
      let a = yield myAjax('...') 
      let b = yield myAjax('...') 
      yield myAjax('...')  
 }
function co(fun) {
  // fun 是生成器函数
    var f = fun()
  function handle(res) {
    if (res.done) return 
    res.value.then(function (data) {
        handle(f.next(data))
    })
  }
  handle(f.next())
}
co(fun)

好了,通过以上的分析,我们看看以下一段代码, 2者的结构是不是很相似呢,其实ES7的async/await就是一个语法糖,其底层帮我们实现了co的操作,不用像ES6 一样需要借助co函数来实现异步了。

function* fun() {
    let a = yield myAjax('...')
  let b = yield myAjax('...')
}
async function fun2() {
    let a = await myAjax('...')
  let b = await myAjax('...')
}

实现一个简单版的promise

我们先熟悉以下promise的一些基本的概念
• Promise 就是一个类,在执行这个类的时候 需要传第一个执行器进去,执行器会立即执行
• Promise 中有三种状态 分别为成功 fulfilled, 失败 rejected, 等待 pending, pending -> fulfilled pending -> rejected 一旦状态确定就不可更改
• resolve 和 reject 函数是用来更改状态的 resolve : fulfilled , reject : rejected
• then 方法内部做的事情就是判断状态 如果状态是成功 就调用成功的回调函数,如果状态是失败 调用失败的回调函数 then 方法是被定义在原型对象当中的
• then 成功回调有一个参数, 表示成功之后的值, 失败回调有一个参数,表示失败的原因

let promise = new Promise ((resolve,reject)=>{
  resolve('成功')
  reject('失败')
})
promise.then(()=>{},()=>{})
实现一个最基本的promise,同步的情况
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
  constructor(execurot){
    execurot(this.resolve,this.reject);//立即执行的执行器
  }
  // promise 状态
  status = PEMDING;
  // 成功之后的值
  value = undefined;
  // 失败的原因
  reason = undefined;
  resolve = value => { //箭头函数是为了避免 this 指向出问题, this 要指向class本身
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 将状态改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
  }
  reject = reason => {
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 将状态改为失败
    this.status = REJECTED;
    // 保存失败之后的原因
    this.reason = reason
  }
  then(successCallback,failCallback){
    // 判断状态
    if(this.status === FULFILLED){
      successCallback(this.value)
    }else if(this.status === REJECTED){
      failCallback(this.reason);
    }
  }
}
考虑异步的情况
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
  constructor(execurot){
    execurot(this.resolve,this.reject);//立即执行的执行器
  }
  // promise 状态
  status = PEMDING;
  // 成功之后的值
  value = undefined;
  // 失败的原因
  reason = undefined;
  // 成功回调
  successCallback = undefined;
  // 失败回调
  failCallback = undefined;
  resolve = value => { //箭头函数是为了避免 this 指向出问题 要指向class
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 将状态改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
    // 判断成功回调是否存在
    this.successCallback && this.successCallback(this.value);
  }
  reject = reason => {
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 将状态改为失败
    this.status = REJECTED;
    // 保存失败之后的原因
    this.reason = reason
    // 判断失败回调是否存在
    this.failCallback && this.failCallback(this.reason);
  }
  then(successCallback,failCallback){
    // 判断状态
    if(this.status === FULFILLED){
      successCallback(this.value)
    }else if(this.status === REJECTED){
      failCallback(this.reason);
    }else{ 
      // 异步的场景,代表当前状态为等待状态,但是这里只实现了一层then,如果存在链接多个then,后面的会覆盖前面的。
      this.successCallback = successCallback;
      this.failCallback = failCallback;
    }
  }
}
实现then方法链式调用多次
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
  constructor(execurot){
    execurot(this.resolve,this.reject);//立即执行的执行器
  }
  // promise 状态
  status = PEMDING;
  // 成功之后的值
  value = undefined;
  // 失败的原因
  reason = undefined;
  // 成功回调
  successCallback = [];
  // 失败回调
  failCallback = [];
  resolve = value => { //箭头函数是为了避免 this 指向出问题 要指向class
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 讲状态改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
    // 判断成功回调是否存在
    // this.successCallback && this.successCallback(this.value);
    while(this.successCallback.length) this.successCallback.shift()(this.value);
  }
  reject = reason => {
    // 如果状态不为pending 阻止程序向下执行
    if(this.status !== PEMDING) return;
    // 将状态改为失败
    this.status = REJECTED;
    // 保存失败之后的原因
    this.reason = reason
    // 判断失败回调是否存在
    // this.failCallback && this.failCallback(this.reason);
    while(this.failCallback.length) this.failCallback.shift()(this.reason);
  }
  then(successCallback,failCallback){
    // 判断状态
    if(this.status === FULFILLED){
      successCallback(this.value)
    }else if(this.status === REJECTED){
      failCallback(this.reason);
    }else{ 
      //代表当前状态为等待状态,此时是以一个数组的形式存放所有的回调。
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback);
    }
  }
}
let promise = new Promise ((resolve,reject)=>{
  resolve('成功')
})
promise.then(()=>{},()=>{}).then(()=>{},()=>{}).then(()=>{},()=>{})

这种情况是没有考虑到then的回调函数中如果返回的还是promise的场景。笔者有空的时候,会继续完善promise 的实现。争取实现一个符合promiseA+规范的。

posted @ 2021-06-14 22:37  eastsae  阅读(55)  评论(0编辑  收藏  举报