JavaScript——异步编程

一、JS是单线程语言

JavaScript 语言是运行在浏览器端的,其目的是为了实现页面的动态交互,实现页面交互,最终就是操作DOM,就决定了必须使用单线程,否则会出现很复杂线程同步问题,比如js中有多个线程一起工作,其中一个线程修改了DOM元素,另一个同时需要删除这个DOM ,此时浏览器就不能够明确的以那个线程为主。解决这个问题,js 就设计成了单线程的模式,任务一个一个排队执行,此时单线程模式上,如果出现耗时的任务,其他任务等待时间较长,会出现假死现象。为了解决这种问题,js有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

同步模式:指的是代码的任务依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序的执行顺序和代码的编写顺序是完全一致的。在单线程模式下,大多数任务都会以同步模式执行。

异步模式:不会去等待这个任务的结束才开始下一个任务,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。异步模式对于JavaScript语言非常重要,没有它就无法同时处理大量的耗时任务。对于开发者而言。单线程下面的异步最大的难点就是代码执行的顺序混乱。

二、异步处理的几种方式

1、回调函数,

2、事件监听(事件绑定),

3、发布/订阅,

4、promise对象(promise模式)

5、async/await

三、Promise

1、基本使用

var promise = new Promise(function(resolve, reject) {
    //这里用于兑现承诺
    resolve(100);
    
    reject(new Error('promise rejected'));  //失败的承诺
})

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

console.log('end')  //  先打印出end,再打印Error

2. 通过Promise封装ajax

//promise 使用案例 (通过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/json').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)
})

4、Promise异常处理

then中回调的onRejected方法,.catch()(推荐)

promise中如果有异常,都会调用reject方法,还可以使用.catch(),使用.catch方法更为常见,因为更加符合链式调用

ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  }).catch(function onRejected(error) {
    console.log('onRejected', error)
  })

// 相当于

ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .then(undefined, function onRejected(error) {
    console.log('onRejected', error)
  })

.catch形式和前面then里面的第二个参数的形式,两者异常捕获的区别:.catch()是对上一个.then()返回的promise进行处理,不过第一个promise的报错也顺延到了catch中,而then的第二个参数形式,只能捕获第一个promise的报错,如果当前then的resolve函数处理中有报错是捕获不到的。

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)
})

还可以在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,不推荐使用

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) => {
  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)
})

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个打印

8、生成器函数Generator异步方案

(1)函数生成器特点是函数名前面有一个‘*’

(2)通过调用函数生成一个控制器

(3)调用next()方法开始执行函数

(4)遇到yield函数将暂停

(5)再次调用next()继续执行函数        

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 }

注意: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"}

9、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"}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值