大前端学习1-1__深入javascript_异步编程

异步编程

  • 异步编程
    • 1. 同步
    • 2. 异步
    • 3. 回调函数
    • 4. Promise
    • 5. Promise 执行时序
    • 6. Generator
    • 7. Async/Await

异步编程

  • js 采用单线程原因

    • js 作为运行在浏览器端的脚本语言 实现页面动态交互就是 dom 操作
    • js 执行环境当中 执行代码的列队只有一个,执行一个任务 进入任务队列进行排队
  • js 采用单线程的优缺点

    • 优点:任务清晰 依次执行
    • 缺点:耗时任务会阻塞阻碍下面任务的执行

    js 把执行模式分为 同步和异步模式进行执行

1. 同步

  • 单线程采用排队执行的机制,配合EventLoop来对任务进行等待和发送

  • js 引擎维护了一个正在执行的表 记录当前做的事情

  • 工作表中任务清空,结束任务

  • 延迟称为阻塞 ,即表现为界面卡顿和卡死现象, 异步模式的存在是为了解决耗时操作。

console.log('start');
function bar(){
     
    console.log('bar');
}
function foo(){
     
    console.log('foo');
    bar()
}
foo();
console.log('end');

2. 异步

不会等待上一个任务的结束,开启过后立即执行下一个任务,耗时任务的后续逻辑一般通过回电函数的方式来定义

  • 异步模式存在的必要性:单线程的 js 语言无法同时处理大量的耗时任务
  • 异步执行顺序相对混乱
console.log('start');

setTimeout(()=>{
     
    console.log('timer1');
},1800)
setTimeout(()=>{
     
    console.log('timer2');
    setTimeout(()=>{
     
        console.log('inner');
    },1000)
},1000)
console.log('end');
// start
// end
// timer2
// timer1
// inner

3. 回调函数

  • 回调函数式所有异步编程方案的根基
  • 可以理解为一件想要做的事情

异步实现方式 回调函数 事件机制 和发布订阅 ,事件机制 和发布订阅 也是基于回调函数的

function foo(cb) {
     
    setTimeout(()=>{
     
        cb()
    },1000)
}

foo(()=>{
     
    console.log('回调');
    console.log('调用者定义,执行者执行');
    console.log('就是调用者告诉执行者异步任务结束后应该做什么');
})

// 回调地狱

4. Promise

  • Promise 是为了不产生回调地狱的

  • commonjs 提出了 Promise 的规范

  • Promise 是一个对象 - 有三种状态, 开始 pending 成功 Fulfillted 失败 Rejected

  • 开始 pending 可能成功 Fulfillted 执行成功后的方法 onFulfillted 可能失败 Rejected 执行失败的方法 onRejected

  • 基本示例

const promise = new Promise((resolve,reject)=>{
     
    resolve(100)
})

promise.then(value=>{
     
    console.log(value);
},err=>{
     
    console.log(err);
})
console.log('end');
  • promise - ajax
function ajax(url) {
     
    return new Promise((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(res=>{
     
    console.log(res);
},err=>{
     
    console.log(err);
})
// 嵌套使用 Promise 是最常见的误区
ajax('/api/urls.json').then(function (urls) {
     
  ajax(urls.users).then(function (users) {
     
    ajax(urls.users).then(function (users) {
     
      ajax(urls.users).then(function (users) {
     
        ajax(urls.users).then(function (users) {
     

        })
      })
    })
  })
})

  • promise链式调用
function ajax(url) {
     
    return new Promise((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(res=>{
     
        console.log(res);
        return ajax('./api/users.json')
    })
    .then(res=>{
     
        console.log(res);
        return ajax('./api/users.json')
    })
    .then(res=>{
     
        console.log(res);
        return ajax('./api/users.json')
    })
    .then(res=>{
     
        console.log(res);
        return 'foo'
    })
    .then(res=>{
     
        console.log(res);
    })

  • Promise 的误区
    1. Promise 嵌套 毫无意义
    2. then 返回全新的 Promise 对象,可以使用链式, 保证异步任务扁平化
    3. 后面 then 方法是在为上一个 then 返回的 Promise 对象注册回调函数
    4. 前面的 then 法法回调函数的返回值会作为后面 then 的回调参数
    5. 事件循环和消息队列
    6. 回调中返回的 Promise 后面的 then 方法回调会等待上一个结束

因为 Promise 对象的then方法返回的是一个新的Promise对象,所以不会进入回调地狱,但是需要使用 then 方法写链式编程

  • promise异常处理
    onRejected 回调主要是处理异常 Promise 失败或者异常都会调用
  ajax('./api/users.json')
        .then(function onFulfilled(res) {
     
            console.log('onFulfilled', res)
        }).catch(function onRejected(err) {
     
            console.log('onRejected', err);
        })

    ajax('./api/users.json')
        .then(function onFulfilled(res) {
     
            console.log('onFulfilled', res);

        }).then(function onFulfilled(res) {
     
            console.log('onFulfilled', res);

        })
  • then 方法是给上一个 Promise 对象注册是成功或者失败的回调 ,因为是链式写法异常会一直传递,所以后面的才可以捕获到第一个的异常
  • catch 只能捕获第一个 Promise 对象的异常
  • 如果用 then 的链式调用 其中一个的发生异常我们会不知道是哪个发生的,

Promise 发生异常会一直向后传递直到被捕获,catch 像给整个联调注册回调更合适

捕获异常不建议注册全局的异常捕获事件,而是应该在每一个 Promise 对象的后面注册 catch 去捕获当前的 Promise 对象是否存在异常

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

// 使用catch注册失败回调
ajax('./api/users.json')
    .then(res=>{
     
        console.log(res);
        return ajax('./api/users.json')
    })
    .catch(err=>{
     
        console.log(err);
    })

// then(onRejected) === then(undefined,onRejected)
ajax('./api/users.json')
    .then(res=>{
     
        console.log(res);
        return ajax('./api/users.json')
    }).then(undefined,(err)=>{
     
        console.log(err);
    })

// 全局捕获 promise异常
window.addEventListener('unhandlerejection',event=>{
     
    const {
     reason,promise} = event
    console.log(reason,promise);
    event.preventDefault();
},false)

// node中的方式
process.on('unhandledRejection',(reason,promise)=>{
     
    console.log(reason,promise);
})

  • Promise 的静态方法

    1. Promise.resolve() 作用:把一个值转为 Promise 对象
// 方式一
        Promise.resolve('foo')
            .then(function (value) {
     
                console.log(value);
            })
// 方式二
        new Promise(function (resolve, reject) {
     
            resolve('foo')
        })

        // 在把一个字符串转为promise对象上时相等的
 如果传入的是一个 promise 对象会原样返回
  1. Promise.reject 把传入数据当做 promise 对象失败的原因
   Promise.reject(new Error('reject'))
   .catch(function (err) {
     
   console.log(error);
   })
  • Promise 并行执行

  • all 方法 可以统一管理所有的 promise 对象,所有的 promise 对象都成功了会返回一个 promis 对象,如果失败了就会返回失败的对象

  • race 方法 可以把 promise 对象组合 但是不会等待其他的 promise 对象执行,只要有一个执行结束成功就成功了

5. Promise 执行时序

  • promise 和 setTimeout 同时存在会先执行 promise 后执行 setTimeOut

  • 排队的队列也就是回调队列 ,回调队列中的任务称为宏任务
    宏任务执行中有一些临时的需求的时候,可以作为一个新的宏任务去队列的最后面进行排队,也可以作为当前任务的微任务,在当前宏任务执行结束后立即执行

promise 回调作为微任务执行的 本轮调用的末尾进行执行,但是 setTimeout 是以宏任务进入到了列队的末尾进行排队的,所以后执行 setTimeout

  • 微任务
    • 微任务的目的就是为了提高整体的响应能力
    • 微任务的方法 Promise & MutationObserver process.nextTick 都会作为微任务在本轮的末尾执行.
console.log('start');
setTimeout(()=>{
     
    console.log('setTimeout');
},0)

Promise.resolve().then(()=>{
     
    console.log('promise');
}).then(()=>{
     
    console.log('promise1');
})

console.log('end');

6. Generator

function * foo(){
     
    console.log('start');
    
    try {
     
        const res = yield 'foo'
        console.log(res);
    } catch (error) {
     
        console.log(error);
    }
}
const generator = foo();
const res = generator.next();
console.log(res);

// generator.throw(new Error('xxx error'))



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/urls11.json')
    console.log(urls)
  } catch (e) {
     
    console.log(e)
  }
}

function co (generator) {
     
  const g = generator()

  function handleResult (result) {
     
    if (result.done) return // 生成器函数结束
    result.value.then(data => {
     
      handleResult(g.next(data))
    }, error => {
     
     // 处理失败
      g.throw(error)
    })
  }

  handleResult(g.next())  // 传入第一次调用生成器函数
}
// co(main)是一个异步方案的库  但是有了async 和 await 之后就很少用co了
co(main)
 

7. Async/Await

 // async 和genarator的区别 
 // 1. 写法上在function前加async 去掉 函数名前的* ,
 // 2. yield 换成 await
 // 3. 函数调用返回了一个promise对象 不需要用co库
 // 4. await只能出现在async的内部去使用 
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)
  }
}

// co(main)
const promise = main() // main函数调用返回了一个promise对象


你可能感兴趣的:(大前端学习,javascript)