Bootstrap

JS异步编程的一些理解

文章内容输出来源:拉勾教育前端高薪训练营

异步编程是为了解决同步模式的一些痛点,同步模式中任务是依次执行,后一个任务必须要等待前一个任务结束后才能开始执行,当某个函数耗时过长时就可能造成页面的假死和卡顿,而异步编程中,后一个任务不会去等待前一个任务结束后才开始,当前一个任务开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。

1.同步模式和异步模式

  • 同步模式代码按顺序执行,任务执行完代码才会继续往下走,可能让页面卡顿和假死
// 同步模式
console.log('global begin')
function bar () {
    console.log('bar task') 
}
function foo () {
    console.log('foo task')
    bar()
}
foo()
console.log('global end')

// global begin
// foo task
// bar task
// global end
  • 异步模式就是下达这个任务开启的指令之后代码就会继续执行,代码不会等待任务的结束
// 异步模式
console.log('global begin')
// 延时器
setTimeout(function timer1 () {
    console.log('timer1 invoke')
}, 1800)
// 延时器中又嵌套了一个延时器
setTimeout(function timer2 () {
    console.log('timer2 invoke')
    setTimeout(function inner () {
        console.log('inner invoke')
    }, 500)
}, 1000)
console.log('global end')

// global begin
// global end
// timer2 invoke
// inner invoke
// timer1 invoke

2.回调函数

由调用者定义,交给执行者执行的函数,回调函数的实际就是把函数作为参数传递,缺点是不利于阅读,执行顺序混乱

function foo(callback) {
  setTimeout(function(){
      callback()
  }, 3000)
}

foo(function() {
  console.log('这就是一个回调函数')
  console.log('调用者定义这个函数,执行者执行这个函数')
  console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})

3.Promise、微任务和宏任务

3.1 Promise

为了避免异步回调地狱,CommonJS社区提出了Promise的规范

  • Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败
  • 任务状态确定后不可以再次更改,pending => fulfilled || pending => rejected
  • promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

Promise静态方法:Promise.resolve(),Promise.reject(),Promise.all()和Promise.race(),接收一个数组,all等待所有任务结束,race只等待第一个任务结束

使用promise封装ajax

function ajax(url) {
  return new Promise(function (resolve, reject) {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}
3.2 微任务和宏任务

宏任务为宿主发起(Node,浏览器),微任务为js引擎发起;微任务Aa必然是某个宏任务A执行的时候创建的,在微任务Aa执行完毕后,开始下一个宏任务B之前,会将A执行期间产生的所有微任务都执行完毕;在node环境下,process.nextTick的优先级高于Promise,也就是说:在宏任务结束后会先执行微任务队列中的nextTickQueue,然后才会执行微任务中的Promise

  • 浏览器宏任务:setTimeout,setInterval,requestAnimationFrame
  • 浏览器微任务:MutationObserver,Promise.then catch finally
  • Node宏任务:setTimeout,setInterval,setImmediate
  • Node微任务:process.nextTick,Promise.then catch finally
  • promise的构造函数是同步执行,promise.then中的函数是异步执行
// 1-7-8-2-4-5-9-11-12
//主线程直接执行
console.log('1');
//丢到宏事件队列中
setTimeout(function() {
    console.log('2');
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
//主线程直接执行
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
//丢到宏事件队列中
setTimeout(function() {
    console.log('9');
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
3.3 模拟Promise
// 定义promise的三种状态,pending(等待),fulfilled(成功),rejected(失败)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor (executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  // 初始化promise状态
  status = PENDING
  // 成功返回值
  value = undefined
  // 失败返回值
  err = undefined
  // 存储异步成功回调函数
  successCallback = []
  // 存储异步失败回调函数
  failCallback = []

  static all (arr) {
    const results = []
    const len = arr.length
    let index = 0
    return new MyPromise((resolve, reject) => {
      const addData = (key, value) => {
        results[key] = value
        index += 1
        if (index === len) {
          resolve(results)
        }
      }
      for (let i = 0; i < len; i += 1) {
        if (arr[i] instanceof MyPromise) {
          arr[i].then(value => {
            addData(i, value)
          }, err => {
            reject(err)
          })
        } else {
          addData(i, arr[i])
        }
      }
    })
  }

  static race (arr) {
    const len = arr.length
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < len; i += 1) {
        if (arr[i] instanceof MyPromise) {
          arr[i].then(value => {
            resolve(value)
          }, err => {
            reject(err)
          })
        } else {
          resolve(arr[i])
        }
      }
    })
  }

  static resolve (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }

  static reject (err) {
    return new MyPromise((resolve, reject) => reject(err))
  }

  resolve = value => {
    // 当状态不为等待时阻止向下执行
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }

  reject = err => {
    // 当状态不为等待时阻止向下执行
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.err = err
    while (this.failCallback.length) this.failCallback.shift()()
  }

  then (successCallback, failCallback) {
    // 将then的参数变为可选参数
    successCallback = successCallback || (value => value)
    failCallback = failCallback || (err => { throw err })
    // 为了实现链式调用,返回一个新的promise
    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const x = successCallback(this.value)
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = failCallback(this.err)
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);
      } else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              const x = successCallback(this.value)
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              const x = failCallback(this.err)
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
      }
    })
    return promise
  }

  finally (callback) {
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
      callback()
    }, err => {
      return MyPromise.resolve(callback()).then(() => { throw err })
    })
  }

  catch (callback) {
    return this.then(undefined, callback)
  }
}

/* 
  判断x的值是普通值还是promise对象
  普通值直接调用resolve
  promise对象根据返回结果决定调用resolve还是reject
*/
function resolvePromise (promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
  }
  if (x instanceof MyPromise) {
    x.then(value => {
      resolve(value)
    }, err => {
      reject(err)
    })
  } else {
    resolve(x)
  }
}

module.exports = MyPromise

4.生成器函数generator

基本使用

function * foo () {
  try {
    const res = yield 'foo'
    console.log(res) // bar
  } catch (e) {
    console.log(e)
  }
}

const generator = foo()
const result = generator.next()
console.log(result) // {value: "foo", done: false}
generator.next('bar')

模拟异步变同步

function ajax(url) {
  return new Promise(function (resolve, reject) {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

function * main () {
  try {
    const users = yield ajax('api/users.json')
    console.log(users)
    const posts = yield ajax('api/posts.json')
    console.log(posts)
  } catch (e) {
    console.log(e)
  }
}

function co (generator) {
  const m = generator()
  const resultAjax = m.next()

  function resultLoop (result) {
    if (result.done) return
    result.value.then(data => {
      const res = m.next(data)
      resultLoop(res)
    }, err => {
      m.throw(err)
    })
  }
  resultLoop(resultAjax)
}

co(main)
;