大前端学习--异步编程

异步编程

一、JS是单线程语言

JS执行环境找那个负责执行代码的线程只有一个

执行任务的模式有两种:同步模式、异步模式。


二、Promise

1. 基本使用
// Promise 基本演示

const promise = new Promise(function (resolve, reject) {
  // 这里用于兑现承诺
  // resolve(100) // 承诺达成

  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end') // 先打印出end,再打印Error
2. 通过Promise封装ajax
function ajax(url) {
  return new Promise(function(resolve, reject) {
    var 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()
  })
}

ajax('/api/users.json2').then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})
3. Promise通过链式调用避免回调嵌套
  • Promise对象的then方法会返回一个全新的Promise对象
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调返回的是Promise,那后面then方法的回调会等待它的结束
function ajax(url) {
  return new Promise(function(resolve, reject) {
    // foo()
    var 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()
  })
}

var promise = ajax('/api/users.json')

// then 方法返回一个全新的promise对象
var promise2 = promise.then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})

console.log(promise2 === promise) // false

// 每一个then方法都是在为上一个then方法添加状态明确过后的回调
ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users.json')
}) // => Promise
.then(function (value) {
  console.log('yi', value)
  console.log(222)
  return 'foo'
}) // => Promise
.then(function (value) {
  console.log(333)
  console.log('jal', value)
}) // => Promise
.then(function (value) {
  console.log(444)
  console.log('ji', value)
}).catch(function onRejected(error) {
  console.log('onRejected', error)
})

其中catch也是then的别名

.catch(function onRejected(error) {
  console.log('onRejected', error)
})
// 就相当于
.then(undefined, function (value) {
  console.log(444)
  console.log('ji', value)
})

then中的第二个参数是reject函数,catch中的参数也是reject函数,但是作用不太相同,then中的reject不能捕获到第一个参数中的resolve中的异常,但是catch由于链式作用,能捕获到前面任意处的异常

ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users2.json') // 这个异常无法捕获
}, function onRejected (e){
  console.log('reject', e)
}) 

推荐使用catch捕获异常,可以捕获整个promise链条上的异常:

ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users2.json')
}).catch(function onRejected (e){
  console.log('reject', e)
})

还可以全局捕获异常:

浏览器中,在window对象上注册事件:

window.addEventListener('unhandledrejection', event => {
  const {reason, promise} = event
  console.log(reason, promise)
  // reason => Promise 失败原因,一般是一个错误对象
  // promise => 出现异常的Promise对象
  event.preventDefault()
}, false)

node中:

process.on('unhandledRejection', (reason, promise) => {
  const {reason, promise} = event
  console.log(reason, promise)
  // reason => Promise 失败原因,一般是一个错误对象
  // promise => 出现异常的Promise对象
})

尽量在代码中明确捕获每一个可能的异常,而不是丢给全局处理。

5. Promise静态方法
  • Promise.resolve() 快速的把一个值转化为Promise对象

    Promise.resolve('foo')
    .then(function (value) {
      console.log(value) // 'foo'
    })
    
    // Promise.resolve('foo') 等价于
    new Promise(function (resolve, reject){
      resolve('foo')
    })
    
    var promise = ajax('/api/users.json')
    var promise2 = Promise.resolve(promise)
    console.log(promise === promise2) // true
    
    // 带有then方法的对象,就是实现了thenable接口, 可以被then的对象
    Promise.resolve({
      then: function (onFulfilled, onRejected) {
        onFulfilled('foo')
      }
    }).then(function (value){
      console.log(value) // foo
    })
    
  • Promise.reject(err) 传入的对象为失败的原因

    Promise.reject('anything')
    .catch(function (err) {
      console.log(err) // anything
    })
    
6. Promise并行执行
  • Promise.all() 等待所有任务成功结束了,才算结束

    var promise = Promise.all([
      ajax('/api/users.json'),
      ajax('/api/posts.json'),
    ])
    
    // 只有promise里面的每一个任务都执行成功了才进入resolve
    // 其中任何一个失败了,都会进入catch
    promise.then(function (values) {
      console.log(values) // 返回一个数组
    //   Array(2)
    // 0:
    // username: "yibo"
    // __proto__: Object
    // 1:
    // name: "jiailing"
    }).catch(function(err){
      console.log(err)
    })
    
    ajax('/api/urls.json')
    .then( value => {
      const urls = Object.values(value)
      const tasks = urls.map(url => ajax(url))
      return Promise.all(tasks)
    })
    .then(values => {
      console.log(values)
    })
    
  • Promise.race() 只会等待第一个结束的任务

    const request = ajax('/api/posts.json')
    const timeout = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('timeout'))
      }, 500);
    })
    Promise.race([
      request, timeout
    ])
    .then(value=>{
      console.log(value)
    })
    .catch(error=>{
      console.log(error)
    })
    // 实现ajax请求超时控制的一种方式
    
7. 微任务

即使Promise中没有任何异步操作,它的回调函数仍然会进入到回调队列中排队。必须等待所有同步代码执行完毕后,Promise中的代码才会被调用。

JS回调队列中的任务称之为【宏任务】,而宏任务执行过程中可以临时加上一些额外需求,可以选择作为一个新的宏任务进到队列中排队(如setTimeout),也可以作为当前任务的【微任务】,直接在当前任务结束后立即执行。

Promise的回调会作为微任务执行。微任务的目的是为了提高整体的响应能力,目前绝大多数异步调用都是作为宏任务执行,Promise 、MutationObserver、process.nextTick 是作为微任务在本轮调用的末尾执行。

console.log('global start') // 第一个打印

Promise.resolve()
.then(()=>{
  console.log('promise')// 第3个打印
})
.then(()=>{
  console.log('promise 2')// 第4个打印
})
.then(()=>{
  console.log('promise 3')// 第5个打印
})
console.log('global end')// 第2个打印

setTimeout属于宏任务

console.log('global start') // 第一个打印

setTimeout(() => {
  console.log('last') // 最后调用
}, 0);

Promise.resolve()
.then(()=>{
  console.log('promise')// 第3个打印
})
.then(()=>{
  console.log('promise 2')// 第4个打印
})
.then(()=>{
  console.log('promise 3')// 第5个打印
})
console.log('global end')// 第2个打印

三、Generator异步方案

1. Generator的基本使用

生成器函数会返回一个生成器对象,调用这个生成器对象的next方法,才会让函数体执行,一旦遇到了yield关键词,函数的执行则会暂停下来,yield后面的值作为next函数的结果返回,如果继续调用函数的next函数,则会再上一次暂停的位置继续执行,知道函数体执行完毕,next返回的对象的done就变成了true

function * fn () {
  console.log(111)
  yield 100
  console.log(222)
  yield 200
  console.log(333)
  yield  300
}

const generator = fn()
console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }
2. Generator实现异步

注意:generator.next(value)中,next传入的参数会作为上一次yield的返回值。

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var 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 () {
  const users = yield ajax('/api/users.json')
  console.log(users)
  
  const posts = yield ajax('/api/posts.json')
  console.log(posts)

  const urls = yield ajax('/api/urls.json')
  console.log(urls)
}
// 调用生成器函数得到一个生成器对象
const generator = main()

// 递归实现generator.next()的调用,直到done为true终止
function dfs(value) {
  const result = generator.next(value)
  if(result.done) return
  result.value.then(data=>{
    console.log(data)
    dfs(data)
  })
}

dfs()
// 打印结果
// Generator实现异步.js:35 {username: "yibo"}
// Generator实现异步.js:19 {username: "yibo"}
// Generator实现异步.js:35 {posts: "jiailing"}
// Generator实现异步.js:22 {posts: "jiailing"}
// Generator实现异步.js:35 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:25 {posts: "/api/posts.json", users: "/api/users.json"}

封装生成器函数执行器co

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var 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)

    const urls = yield ajax('/api/urls.json')
    console.log(urls)
  } catch(e) {
    // 如果生成器函数中,发生了异常,会被生成器对象的throw方法捕获
    console.log(e)
  }
}

// 封装了一个生成器函数执行器
function co(main) {
  // 调用生成器函数得到一个生成器对象
  const generator = main()

  // 递归实现generator.next()的调用,直到done为true终止
  function handleResult(result) {
    if(result.done) return
    result.value.then(data=>{
      console.log(data)
      handleResult(generator.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(generator.next())
}

co(main)

// Generator实现异步.js:42 {username: "yibo"}
// Generator实现异步.js:20 {username: "yibo"}
// Generator实现异步.js:42 {posts: "jiailing"}
// Generator实现异步.js:23 {posts: "jiailing"}
// Generator实现异步.js:42 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:26 {posts: "/api/posts.json", users: "/api/users.json"}

四、Async/Await 语法糖

await关键词只能出现在async函数中。

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var 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()
  })
}

async function main () {
  try {
    const users = await ajax('/api/users.json')
    console.log(users)
    
    const posts = await ajax('/api/posts.json')
    console.log(posts)

    const urls = await ajax('/api/urls.json')
    console.log(urls)
  } catch(e) {
    console.log(e)
  }
}

main()

// async-await.js:20 {username: "yibo"}
// async-await.js:23 {posts: "jiailing"}
// async-await.js:26 {posts: "/api/posts.json", users: "/api/users.json"}

猜你喜欢

转载自blog.csdn.net/jal517486222/article/details/106212764