精华内容
下载资源
问答
  • JS异步编程

    2021-03-01 09:14:56
    JS异步编程JS 异步编程方案JS 异步编程的实现方式深入理解异步编程的核心 Promise理解 Generator、Async/await 等异步编程的语法糖GeneratorAsync/await JS 异步编程方案 JS异步编程的使用 回调函数、事件监听、...

    JS 异步编程方案

    JS异步编程的使用

    回调函数、事件监听、Promise、Generator、async/await

    什么是同步
    同步就是在执行某段代码时,在该代码没有得到返回结果之前,其他代码暂时是无法执行的,但是一旦执行完成拿到返回值之后,就可以执行其他代码了。换句话说,在此段代码执行完未返回结果之前,会阻塞之后的代码执行,这样的情况称为同步。

    什么是异步
    异步就是当某一代码执行异步过程调用发出后,这段代码不会立刻得到返回结果。而是在异步调用发出之后,一般通过回调函数处理这个调用之后拿到结果。异步调用发出后,不会影响阻塞后面的代码执行,这样的情形称为异步。

    JS 编程中为什么需要异步
    我们都知道 JavaScript 是单线程的,如果 JS 都是同步代码执行意味着什么呢?这样可能会造成阻塞,如果当前我们有一段代码需要执行时,如果使用同步的方式,那么就会阻塞后面的代码执行;而如果使用异步则不会阻塞,我们不需要等待异步代码执行的返回结果,可以继续执行该异步任务之后的代码逻辑。因此在 JS 编程中,会大量使用异步来进行编程。

    JS 异步编程的实现方式

    回调函数

    使用回调函数来实现存在一个很常见的问题: 回调地狱

    fs.readFile(A, 'utf-8', function(err, data) {
        fs.readFile(B, 'utf-8', function(err, data) {
            fs.readFile(C, 'utf-8', function(err, data) {
                fs.readFile(D, 'utf-8', function(err, data) {
                    //....
                });
            });
        });
    });
    

    回调实现异步编程的场景也有很多,比如:

    • ajax 请求的回调
    • 定时器中的回调
    • 事件回调
    • Nodejs 中的一些方法回调

    异步回调如果层级很少,可读性和代码的维护性暂时还是可以接受,一旦层级变多就会陷入回调地狱,上面这些异步编程的场景都会涉及回调地狱的问题。

    Promise-ECAMSCRIPT2015

    采用 Promise 的实现方式在一定程度上解决了回调地狱的问题

    function read(url) {
        return new Promise((resolve, reject) => {
            fs.readFile(url, 'utf8', (err, data) => {
                if(err) reject(err);
                resolve(data);
            });
        });
    }
    read(A).then(data => {
        return read(B);
    }).then(data => {
        return read(C);
    }).then(data => {
        return read(D);
    }).catch(reason => {
        console.log(reason);
    });
    
    function read(url) {
        return new Promise((resolve, reject) => {
            fs.readFile(url, 'utf8', (err, data) => {
                if(err) reject(err);
                resolve(data);
            });
        });
    }
    // 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
    Promise.all([read(A), read(B), read(C)]).then(data => {
        console.log(data);
    }).catch(err => 
        console.log(err)
    );
    

    Generator-ECAMSCRIPT2015

    最大的特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用 yield 语法来标注。
    Generator 函数一般配合 yield 使用,Generator 函数最后返回的是迭代器。
    参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators

    function* gen() {
      console.log("enter");
      let a = yield 1;
      let b = yield (function () {return 2})();
      return 3;
    }
    var g = gen()           // 阻塞住,不会执行任何语句
    console.log(typeof g)   // 返回 object 这里不是 "function"
    console.log(g.next())
    console.log(g.next())
    console.log(g.next())
    console.log(g.next()) 
    // output:
    // { value: 1, done: false }
    // { value: 2, done: false }
    // { value: 3, done: true }
    // { value: undefined, done: true }
    

    async/await-ECAMSCRIPT2016

    async 是 Generator 函数的语法糖,async/await 的优点是代码清晰(不像使用 Promise 的时候需要写很多 then 的方法链),可以处理回调地狱的问题

    function testWait() {
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                console.log("testWait");
                resolve();
            }, 1000);
        })
    }
    async function testAwaitUse(){
        await testWait()
        console.log("hello");
        return 123;
        // 输出顺序:testWait,hello
        // 第十行如果不使用await输出顺序:hello , testWait
    }
    console.log(testAwaitUse());
    

    深入理解异步编程的核心 Promise

    what

    简单来说它就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。
    从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

    结合JS 异步编程的实现方式-Promise中的代码分析Promise内部的状态流转情况:

    一般 Promise 在执行过程中,必然会处于以下几种状态之一

    • 待定(pending):初始状态,既没有被完成,也没有被拒绝。
    • 已完成(fulfilled):操作成功完成。
    • 已拒绝(rejected):操作失败。
    待定状态的 Promise 对象执行的话,最后要么会通过一个值完成,要么会通过一个原因被拒绝。
    当其中一种情况发生时,我们用 Promise 的 then 方法排列起来的相关处理程序就会被调用。
    因为最后 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是一个 Promise, 所以它们可以继续被链式调用。
    注意:内部状态改变之后不可逆
    

    解决回调地狱

    1. 回调地狱的问题
      • 多层嵌套
      • 每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性
    2. 如何处理
      • 回调函数延迟绑定
      • 返回值穿透
      • 错误冒泡
    let readFilePromise = filename => {
      return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
          if (err) {
            reject(err)
          } else {
            resolve(data)
          }
        })
      })
    }
    readFilePromise('1.json').then(data => {
      return readFilePromise('2.json')
    });
    

    从上面的代码中可以看到,回调函数不是直接声明的,而是通过后面的 then 方法传入的,即延迟传入,这就是回调函数延迟绑定。

    let x = readFilePromise('1.json').then(data => {
      return readFilePromise('2.json')  //这是返回的Promise
    });
    x.then(/* 内部逻辑省略 */)
    

    我们根据 then 中回调函数的传入值创建不同类型的 Promise,然后把返回的 Promise 穿透到外层,以供后续的调用。这里的 x 指的就是内部返回的 Promise,然后在 x 后面可以依次完成链式调用。这便是返回值穿透的效果。

    readFilePromise('1.json').then(data => {
        return readFilePromise('2.json');
    }).then(data => {
        return readFilePromise('3.json');
    }).then(data => {
        return readFilePromise('4.json');
    }).catch(err => {
      // xxx
    })
    

    这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了。

    Promise的静态方法
    1. Promise.all

    语法: Promise.all(iterable)
    参数: 一个可迭代对象,如 Array。
    描述: 此方法对于汇总多个 promise 的结果很有用,在 ES6 中可以将多个 Promise.all 异步请求并行操作,返回结果一般有下面两种情况。

    • 当所有结果成功返回时按照请求顺序返回成功。
    • 当其中有一个失败方法时,则进入失败方法。
    //1.获取轮播数据列表
    function getBannerList(){
      return new Promise((resolve,reject)=>{
          setTimeout(function(){
            resolve('轮播数据')
          },300) 
      })
    }
    //2.获取店铺列表
    function getStoreList(){
      return new Promise((resolve,reject)=>{
        setTimeout(function(){
          resolve('店铺数据')
        },500)
      })
    }
    //3.获取分类列表
    function getCategoryList(){
      return new Promise((resolve,reject)=>{
        setTimeout(function(){
          resolve('分类数据')
        },700)
      })
    }
    function initLoad(){ 
      Promise.all([getBannerList(),getStoreList(),getCategoryList()])
      .then(res=>{
        console.log(res) 
      }).catch(err=>{
        console.log(err)
      })
    } 
    initLoad()
    

    Promise.allSettled

    语法: Promise.allSettled(iterable)
    参数: 一个可迭代对象,如 Array。
    描述: 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管其是否处理成功

    const resolved = Promise.resolve(2);
    const rejected = Promise.reject(-1);
    const allSettledPromise = Promise.allSettled([resolved, rejected]);
    allSettledPromise.then(function (results) {
      console.log(results);
    });
    // 返回结果:
    // [
    //    { status: 'fulfilled', value: 2 },
    //    { status: 'rejected', reason: -1 }
    // ]
    

    Promise.any

    语法: Promise.any(iterable)
    参数: 一个可迭代对象,如 Array。
    描述: any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态。

    const resolved = Promise.resolve(2);
    const rejected = Promise.reject(-1);
    const allSettledPromise = Promise.any([resolved, rejected]);
    allSettledPromise.then(function (results) {
      console.log(results);
    });
    // 返回结果:
    // 2
    

    Promise.race

    语法: Promise.race(iterable)
    参数: 一个可迭代对象,如 Array。
    描述: race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。

    //请求某个图片资源
    function requestImg(){
      var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){ resolve(img); }
        img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
      });
      return p;
    }
    //延时函数,用于给请求计时
    function timeout(){
      var p = new Promise(function(resolve, reject){
        setTimeout(function(){ reject('图片请求超时'); }, 5000);
      });
      return p;
    }
    Promise.race([requestImg(), timeout()])
    .then(function(results){
      console.log(results);
    })
    .catch(function(reason){
      console.log(reason);
    });
    

    总结

    Promise方法 总结
    all 参数所有返回结果为成功才返回
    allSettled 参数不论返回结果是否成功,都返回每个参数执行状态
    any 参数中只要有一个成功,就返回改成功的执行结果
    race 返回最先返回执行成功的参数的执行结果

    理解 Generator、Async/await 等异步编程的语法糖

    Generator

    结合JS 异步编程的实现方式-Generator中的代码分析Generator函数的执行情况:
    Generator 中配合使用 yield 关键词可以控制函数执行的顺序,每当执行一次 next 方法,Generator 函数会执行到下一个存在 yield 关键词的位置。
    总结Generator的执行:

    1. 调用 gen() 后,程序会阻塞住,不会执行任何语句。
    2. 调用 g.next() 后,程序继续执行,直到遇到 yield 关键词时执行暂停。
    3. 一直执行 next 方法,最后返回一个对象,其存在两个属性:value 和 done。

    yield

    yield 同样也是 ES6 的新关键词,配合 Generator 执行以及暂停。yield 关键词最后返回一个迭代器对象,该对象有 value 和 done 两个属性,其中 done 属性代表返回值以及是否完成。yield 配合着 Generator,再同时使用 next 方法,可以主动控制 Generator 执行进度。

    function* gen1() {
        yield 1;
        yield* gen2();
        yield 4;
    }
    function* gen2() {
        yield 2;
        yield 3;
    }
    var g = gen1();
    console.log(g.next())
    console.log(g.next())
    console.log(g.next())
    console.log(g.next())
    // output:
    // { value: 1, done: false }
    // { value: 2, done: false }
    // { value: 3, done: false }
    // { value: 4, done: false }
    // {value: undefined, done: true}
    

    从上面的代码中可以看出,使用 yield 关键词的话还可以配合着 Generator 函数嵌套使用,从而控制函数执行进度。这样对于 Generator 的使用,以及最终函数的执行进度都可以很好地控制,从而形成符合你设想的执行顺序。即便 Generator 函数相互嵌套,也能通过调用 next 方法来按照进度一步步执行。

    thunk 函数

    let isString = (obj) => {
      return Object.prototype.toString.call(obj) === '[object String]';
    };
    let isFunction = (obj) => {
      return Object.prototype.toString.call(obj) === '[object Function]';
    };
    let isArray = (obj) => {
      return Object.prototype.toString.call(obj) === '[object Array]';
    };
    ....
    

    可以看到,其中出现了非常多重复的数据类型判断逻辑,平常业务开发中类似的重复逻辑的场景也同样会有很多。我们将它们做一下封装,如下所示。

    let isType = (type) => {
      return (obj) => {
        return Object.prototype.toString.call(obj) === `[object ${type}]`;
      }
    }
    
    let isString = isType('String');
    let isArray = isType('Array');
    isString("123");    // true
    isArray([1,2,3]);   // true
    

    像 isType 这样的函数我们称为 thunk 函数,它的基本思路都是接收一定的参数,会生产出定制化的函数,最后使用定制化的函数去完成想要实现的功能

    Generator 和 thunk 结合

    const readFileThunk = (filename) => { // thunk 函数
      return (callback) => {
        fs.readFile(filename, callback);
      }
    }
    const gen = function* () {
      const data1 = yield readFileThunk('1.txt')
      console.log(data1.toString())
      const data2 = yield readFileThunk('2.txt')
      console.log(data2.toString)
    }
    let g = gen();
    g.next().value((err, data1) => {
      g.next(data1).value((err, data2) => {
        g.next(data2);
      })
    })
    

    readFileThunk 就是一个 thunk 函数,上面的这种编程方式就让 Generator 和异步操作关联起来了。上面第三段代码执行起来嵌套的情况还算简单,如果任务多起来,就会产生很多层的嵌套,可读性不强,因此我们有必要把执行的代码封装优化一下,如下所示。

    function run(gen){
      const next = (err, data) => {
        let res = gen.next(data);
        if(res.done) return;
        res.value(next);
      }
      next();
    }
    run(g);
    

    我们可以看到 run 函数和上面的执行效果其实是一样的。

    Generator 和 Promise 结合

    // 最后包装成 Promise 对象进行返回
    const readFilePromise = (filename) => {
      return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
          if(err) {
            reject(err);
          }else {
            resolve(data);
          }
        })
      }).then(res => res);
    }
    // 这块和上面 thunk 的方式一样
    const gen = function* () {
      const data1 = yield readFilePromise('1.txt')
      console.log(data1.toString())
      const data2 = yield readFilePromise('2.txt')
      console.log(data2.toString)
    }
    // 这块和上面 thunk 的方式一样
    function run(gen){
      const next = (err, data) => {
        let res = gen.next(data);
        if(res.done) return;
        res.value.then(next);
      }
      next();
    }
    run(g);
    

    thunk 函数的方式和通过 Promise 方式执行效果本质上是一样的,只不过通过 Promise 的方式也可以配合 Generator 函数实现同样的异步操作。

    co 函数库
    co 函数库是著名程序员 TJ 发布的一个小工具,用于处理 Generator 函数的自动执行。核心原理其实就是上面讲的通过和 thunk 函数以及 Promise 对象进行配合,包装成一个库。

    co 的源码库: https://github.com/tj/co/blob/master/index.js

    const co = require('co');
    let g = gen();
    co(g).then(res =>{
      console.log(res);
    })
    

    co 函数库处理原理:

    1. 因为 Generator 函数就是一个异步操作的容器,它需要一种自动执行机制,co 函数接受 Generator 函数作为参数,并最后返回一个 Promise 对象。
    2. 在返回的 Promise 对象里面,co 先检查参数 gen 是否为 Generator 函数。如果是,就执行该函数;如果不是就返回,并将 Promise 对象的状态改为 resolved。
    3. co 将 Generator 函数的内部指针对象的 next 方法,包装成 onFulfilled 函数。这主要是为了能够捕捉抛出的错误。
    4. 关键的是 next 函数,它会反复调用自身。

    Async/await

    // readFilePromise 依旧返回 Promise 对象
    const readFilePromise = (filename) => {
      return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
          if(err) {
            reject(err);
          }else {
            resolve(data);
          }
        })
      }).then(res => res);
    }
    // 这里把 Generator的 * 换成 async,把 yield 换成 await
    const gen = async function() {
      const data1 = await readFilePromise('1.txt')
      console.log(data1.toString())
      const data2 = await readFilePromise('2.txt')
      console.log(data2.toString)
    }
    

    async 函数对 Generator 函数的改进,主要体现在以下三点:

    1. 内置执行器:Generator 函数的执行必须靠执行器,因为不能一次性执行完成,所以之后才有了开源的 co 函数库。但是,async 函数和正常的函数一样执行,也不用 co 函数库,也不用使用 next 方法,而 async 函数自带执行器,会自动执行。
    2. 适用性更好:co 函数库有条件约束,yield 命令后面只能是 Thunk 函数或 Promise 对象,但是 async 函数的 await 关键词后面,可以不受约束。
    3. 可读性更好:async 和 await,比起使用 * 号和 yield,语义更清晰明了。
    展开全文
  • JS 异步编程

    2020-09-28 15:28:01
    JS 异步编程 https://www.jianshu.com/p/9bfb68c585bc JavaScript异步编程 单线程: JS执行环境中负责执行代码的线程只有一个 优点: 更安全更简单 缺点: 耗时任务阻塞 同步模式—调用栈(Call stack)排队执行 异步模式...

    JS 异步编程

    JavaScript异步编程

    单线程: JS执行环境中负责执行代码的线程只有一个
    优点: 更安全更简单
    缺点: 耗时任务阻塞

    同步模式—调用栈(Call stack)排队执行

    异步模式

    不会去等待这个任务的结束才开始下一个任务
    开启过后立即往后执行下一个任务
    后续逻辑一般会通过回调函数的方式定义
    消息队列(Queue)和事件循环(Event loop)
    运行环境提供的API是以同步或异步模式的方式工作
    EventLoop 是一种循环机制 ,不断去轮询一些队列 ,从中找到 需要执行的任务并按顺序执行的一个执行模型。
    消息队列 是用来存放宏任务的队列, 比如定时器时间到了, 定时间内传入的方法引用会存到该队列, ajax回调之后的执行方法也会存到该队列。
    一开始整个脚本作为一个宏任务执行。执行过程中同步代码直接执行,宏任务等待时间到达或者成功后,将方法的回调放入宏任务队列中,微任务进入微任务队列。
    当前主线程的宏任务执行完出队,检查并清空微任务队列。接着执行浏览器 UI 线程的渲染工作,检查web worker 任务,有则执行。
    然后再取出一个宏任务执行。以此循环…
    宏任务可以理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
    浏览器为了让 JS 内部宏任务 与 DOM 操作能够有序的执行,会在一个宏任务执行结束后,在下一个宏任务执行开始前,对页面进行重新渲染。
    宏任务包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、MessageChannel 等
    微任务可以理解是在当前任务执行结束后需要立即执行的任务。也就是说,在当前任务后,在渲染之前,执行清空微任务。
    所以它的响应速度相比宏任务会更快,因为无需等待 UI 渲染。
    微任务包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)等

    Promise

    三个状态 Pending Fulfilled-onFulfilled Rejected-onRejected
    状态更改之后不可修改

    promise 基本用法

    const promise = new Promise(function(resolve, reject){
    // foo()
    // throw new Errow(‘error’)
    resolve(100)

    // reject(new Error("promise rejected"))
    

    })
    // promise.then(function(value){
    // console.log(‘resolved’, value)
    // }, function(error){
    // console.log(‘rejected’, error)
    // })
    // console.log(‘end’)

    Promise 常见误区—回调嵌套

    Promise 链式调用

    then方法会返回一个全新的Promise对象
    后面的then方法就是在为上一个then返回的Promise注册回调
    前面then方法中的回调函数的返回值会作为后面then方法回调的参数
    如果回调中返回的是Promise, 那么后面then方法的回调会等待它的结束
    let promise2 = promise.then(function(value){
    console.log(‘resolved’, value)
    }, function(error){
    console.log(‘rejected’, error)
    }) .then(function(value){
    console.log(‘resolved’, value)
    }, function(error){
    console.log(‘rejected’, error)
    }) .then(function(value){
    console.log(‘resolved’, value)
    }, function(error){
    console.log(‘rejected’, error)
    })
    console.log(promise2 === promise)
    promise.then(function(value){
    console.log(‘resolved’, value)
    }).catch(error => {
    console.log(“error”, error)
    })

    Promise 异常处理

    Promise实例的catch方法 then方法中的失败回调
    区别: (1)then注册失败回调只能捕获到前一个promise的异常
    (2)catch是给整个Promise链条注册的失败回调

    Promise 静态方法

    resolve() reject()
    Promise 静态方法 Promise.resolve() Promise.reject()
    三种是等价的 有thenable接口的函数
    Promise.resolve(‘f00’).then( value => {
    console.log(‘resolve’, value)
    })
    Promise.resolve({
    then: (onFulfilled, onRejected) => {
    onFulfilled(‘f00’)
    }
    }).then(value => {
    console.log(value)
    })
    new Promise((resolve, reject) => {
    resolve(‘f00’)
    }).then(value => {
    console.log(‘new’, value)
    })
    Promise.reject(new Error(‘rejected’)).catch(error => {
    console.log(‘error’, error)
    })

    Promise 并行执行

    all()方法 race()方法

    Promise 执行时序 / 宏任务 微任务

    微任务: 提高整体的响应能力
    Promise 执行时序 / 宏任务 微任务
    console.log(‘global start’)
    setTimeout(()=>{
    console.log(‘setTimeout1’)
    }, 1000)
    setTimeout(()=>{
    console.log(‘setTimeout’)
    }, 100)
    Promise.resolve().then(()=>{
    console.log(‘promise’)
    }).then(()=>{
    console.log(‘promise1’)
    })
    console.log(‘global end’)

    Generator异步方案

    //Generator异步方案
    function *foo(){
    console.log(“start”)
    try{
    const res = yield ‘foo’
    console.log(res, ‘res’)
    }catch(e){
    console.log(e, ‘e’)
    }

    }
    const generator = foo()
    const result = generator.next()
    console.log(result, ‘result’)
    generator.next(‘bar’)
    generator.throw(new Error(‘Generator Error’))
    //管理异步流程 体验Generator 函数异步方案
    function *main(){
    try{
    const users = yield promise
    console.log(‘users’, users)

        const posts = yield Promise.resolve(200)
        console.log(posts, 'posts')
    }catch(e){
        console.log(e)
    }
    

    }
    const g = main()
    const result = g.next()
    result.value.then(data => {
    const result2 = g.next(data)
    if(result2.done) return
    result2.value.then(data => {
    g.next(data)
    })
    })
    //递归执行Generator函数
    const g = main()
    function handleResult (result){
    if(result.done) return
    result.value.then(data => {
    handleResult(g.next(data))
    }, error => {
    g.throw(error)
    })
    }
    handleResult(g.next())
    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 语法糖 语言层面的异步编程标准

    async function mainSync(){
    try{
    const users = await promise
    console.log(‘users’, users)

        const posts = await Promise.resolve(200)
        console.log(posts, 'posts')
    }catch(e){
        console.log(e)
    }
    

    }
    const mains = mainSync()
    mains.then(()=>{
    console.log(‘all completed’)
    })

    展开全文
  • js异步编程

    2020-10-29 00:51:46
    谈谈你是如何理解js异步编程的,EventLoop,消息队列都是做什么的,什么是宏任务,什么是微任务? 1.采用单线程工作的原因 js这门语言刚刚创建出来的时候的特点就是单线程,用来实现页面上的交互,实现交互的核心...

    简答

    一.谈谈你是如何理解js异步编程的,EventLoop,消息队列都是做什么的,什么是宏任务,什么是微任务?

    1.采用单线程工作的原因

    js这门语言刚刚创建出来的时候的特点就是单线程,用来实现页面上的交互,实现交互的核心就是dom操作,必须为单线程,否则就会出现线程同步问题(如删除、移动一起执行),浏览器不知道以哪个线程 为准

    单线程是指js执行环境中负责执行代码的线程只有一个

    JavaScript是单线程的,但浏览器不是单线程,例如JavaScript的api,如倒计时器

    所有的异步编程方案的根基就是回调函数

    缺点:有一步耗时的任务会出现阻塞

    为了解决这个问题,JavaScript将任务的执行模式分为同步模式和异步模式

    2.异步编程

    1.不会等待这个任务的结束才开始下一个任务,开启之后就立即往后执行下一个任务

    2.能解决处理大量耗时任务

    3.代码执行顺序混乱

    3.EventLoop,消息队列

    EventLoop监听调用栈和消息队列,当调用栈中的任务结束后,EventLoop就会从消息队列中取出第一个回调函数压入到调用栈当中执行、

    js代码调用线程开始异步代码,

    4.什么是宏任务,什么是微任务

    宏任务表示比较大的异步任务,会排到消息队列最后,为api的执行任务

    微任务表示小的任务,属于额外的小任务,会排到调用栈的执行完的第一个 如promise

    js在完成主线程之后,会依次执行消息队列当中的任务

    代码

     

    一 将下面异步代码使用promise的方式改进

    setTimeout(function () {
    
        var a = 'hello'
    
        setTimeout(function(){
    
            var b = 'lagou'
    
            setTimeout(function(){
    
                var c = '(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤'
    
                console.log(a+b+c)
    
            },10)
    
        },10)
    
    },10);
    
    var promise = Promise.resolve('hello')
    
    .then(value=>{
    
        var b = 'lagou'
    
        return value + b
    
    })
    
    .then(value=>{
    
        var c = '(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤'
    
        console.log(value + c)
    
    })

    二、基于以下代码完成下面的四个练习

    const fp = require('lodash/fp')
    // 数据
    // horsepower 马力  dollar_value 价格 in_stock 库存
    const cars = [
        { name: 'Ferrari FF', horsepower: 660, dollar_value: 700000, in_stock: true },
        { name: 'Spyker C12 Zagato', horsepower: 650, dollar_value: 648000, in_stock: false },
        { name: 'Jagura XKR-S', horsepower: 550, dollar_value: 132000, in_stock: false },
        { name: 'Audi R8', horsepower: 525, dollar_value: 114200, in_stock: false },
        { name: 'Aston Martin One-77', horsepower: 750, dollar_value: 185000, in_stock: true },
        { name: 'Pagani Huayra', horsepower: 700, dollar_value: 130000, in_stock: false }
    ]

    1.使用函数组合fp.flowRight()实现下面这个函数

    let isLastInStock = function(cars){
        // 获取最后一条数据
        let last_car =  fp.last(cars)
        // 获取最后一条数据的 in_stock 属性值
        return fp.prop('in_stock',last_car)
    }
    let isLastInStock = fp.flowRight(fp.prop('in_stock'),fp.last)
    let value = isLastInStock(cars)
    console.log(value)
    

    2.使用fp.flowRight() fp.prop fp.first 获取第一个car的name

    let getFirstName = fp.flowRight(fp.prop('name'),fp.first)
    let value = getFirstName(cars)
    console.log(value)

    3.使用帮助函数_average重构averageDollarValue,使用函数组合的方式实现

    let _average = function(xs){
        return fp.reduce(fp.add,0,xs)/xs.length
    }
    let averageDollarValue = function(cars){
        let dollar_values = fp.map(function(car){
            return car.dollar_value
        },cars)
        return _average(dollar_values)
    }
    let averageDollarValue = fp.flowRight(_average,fp.map(car=>car.dollar_value))
    console.log(averageDollarValue(cars))

    4.使用flowRight写一个sanitizeNames()函数,返回一个下划线连接的小写字符串,把数组的name转换为这种格式

    // 例如:sanitizeNames['Hello World']=>['hello_word']
    let _underscore = fp.replace(/\W+/g,'_')
    let sanitizeNames = fp.flowRight(fp.map(fp.flowRight(_underscore,fp.toLower)))
    console.log(sanitizeNames(['Hello World']))

    三、基于下面提供的代码,完成后续的四个练习

    // support.js
    class Container{
        static of(value){
            return new Container(value)
        }
        constructor(value){
            this._value = value
        }
        map(fn){
            return Container.of(fn(this._value))
        }
    }
    class Maybe{
        static of(x){
            return new Maybe(x)
        }
        isNothing(){
            return this._value === null || this._value === undefined
        }
        constructor(x){
            this._value = x
        }
        map(fn){
            return this.isNothing()?this:Maybe.of(fn(this._value))
        }
    }
    module.exports = {Maybe,Container}

     1.使用fp.add(x,y)和fp.map(f,x)创建一个能让functor里面的值增加的函数ex1

    const {Maybe,Container} = require('./support')
    let maybe = Maybe.of([5,6,1,'a',undefined])
    let ex1 = (x)=>{
        return maybe.map(fp.map(fp.add(x)))
    }
    console.log(ex1(1))

    2.实现一个函数ex2能够使用fp.first获取列表的第一个元素

    let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
    let ex2 = () => {
        return xs.map(fp.first)._value
    }
    console.log(ex2())

    3.实现一个函数 ex3,使用safeProp和fp.first找到user的名字的首字母

    let safeProp = fp.curry(function (x, o) {
        return Maybe.of(o[x])
    })
    let user = { id: 2, name: 'Albert' }
    let ex3 = () => {
        return safeProp('name')(user).map(fp.first)._value
    }
    console.log(ex3())

    4.使用Maybe重写ex4,不要有if语句

    let ex4 = function(n){
        if(n){
            return parseInt(n)
        }
    }
    let ex4 = function(n) {
        return Maybe.of(n).map(parseInt)._value
    }
    console.log(ex4(null))

     

    展开全文
  • js 异步编程

    2018-10-30 22:54:10
    大家都知道js的执行环境是单线程的,如果没有异步编程,那么js的执行效率会非常低下,导致程序十分卡顿,一提到异步编程大家首先的想到的一定是回调函数,这也是最常用的异步编程的形式,但其实常用的还有Promise和...
        

    大家都知道js的执行环境是单线程的,如果没有异步编程,那么js的执行效率会非常低下,导致程序十分卡顿,一提到异步编程大家首先的想到的一定是回调函数,这也是最常用的异步编程的形式,但其实常用的还有Promise和Async函数,接下来就让我们一起学习这几种常用的异步编程方法。

    回调函数

    回调函数就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数,来看一个简单的例子:

    function print(name, callback) {
      setTimeout(() => {
        console.log(name)
        if (callback) {
          callback()
        }
      }, 1000)
    }
    print('a', function () {
      print('b')
    })

    上面这个例子中将print('b')放在print('a')的回调函数中,这样就能按顺序依次打印a、b,但是回调函数有一个很明显的问题,就是当回调函数嵌套过深时,会导致代码混乱,不够清晰,这就是人们常说的对调地狱,来看下面这个例子:

    function print(name, callback) {
      setTimeout(() => {
        console.log(name)
        if (callback) {
          callback()
        }
      }, 1000)
    }
    print('a', function () {
      print('b', function () {
        print('c', function () {
          print('d')
        })
      })
    })

    当我们想按顺序依次打印a、b、c、d时,代码就变成上面的样子,可以看到,我们的代码形成四层嵌套,如果还要加回调函数就要继续嵌套,这样嵌套会越写越深,越来越难以维护,此时我们就必须考虑用新的技术去改进,es6的Promise函数应运而生,接下来让我们看Promise函数是如何改进这个问题的。

    Promise

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve()
        }, 1000)
      })
    }
    print('a').then(() => {
      return print('b')
    })
      .then(() => {
        return print('c')
      })
      .then(() => {
        return print('d')
      })

    和之前用回调函数的形式相比,Promise函数写法更加清晰,由回调函数的嵌套调用变成了链式调用,但是Promise也有一个很严重的问题就是代码冗余,原来的任务被Promise包装了一下,不管什么操作都是放在then函数里面,导致代码的语以变差,有什么更好的解决办法呢?如果您对Promise函数还想有更深入的了解,可以去看阮一峰老师es6入门

    Async

    在正式使用异步函数之前,先简单的介绍一下它的用法,async通常与await一起使用,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体后面的语句。做了简单的介绍后,接下来,我们来async函数是怎么对Promise调用优化的。看下面的例子:

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve()
        }, 1000)
      })
    }
    async function test () {
      await print('a')
      await print('b')
      await print('c')
      await print('d')
    }
    test()

    async函数来处理之前的问题,代码就是上面的这个例子中所展示的样子,是不是感觉代码瞬间清晰了,而且代码更加好理解了,再仔细思考一下使用async异步函数就很完美了吗?其实async异步函数也有其固有的问题,接下来我们就看看async异步函数还有什么问题需要解决。

    错误捕获

    异步函数第一个需要解决的问题就是错误捕获的问题,让我们看看一般情况下async异步函数是怎么做错误捕获的,来看一个例子:

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve()
        }, 1000)
      })
    }
    async function test () {
      try {
        await print('a')
      } catch (err) {
        console.log(err)
      }
    }
    test()

    当使用上述形式的try,catch进行错误捕获的时候,是不是觉得代码和使用Promise函数时一样啰嗦,那有没有好的解决办法呢?让我们来看另外一个例子:

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve('a')
        }, 1000)
      })
    }
    async function test () {
      let [ err, result ] = await to(print('a'))
      if (err) throw err
      return result
    }
    test()

    to.js:

    function to(promise, errorExt) {
      return promise
        .then(function (data) { return [null, data]; })
        .catch(function (err) {
          if (errorExt) {
            Object.assign(err, errorExt);
          }
          return [err, undefined];
        });
    }
    
    export { to };
    export default to;    

    上述例子中,将async异步函数的错误处理封装到了一个to.js中,这里面其实只有一个简单方法,传入一个Promise对象,对Promise对象进行错误捕获返回值,用解构的形式获取返回值和错误,这样就不需要反复写try catche做错误捕获了。to.js是一个开源库

    异步陷阱

    什么是异步陷阱呢?在使用async异步函数的时候,多个异步操作是可以同时执行,但是有await命令变成了继发的形式了,即必须等待前一个执行完了后一个才能执行,还是之前的例子:

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve()
        }, 1000)
      })
    }
    async function test () {
      await print('a')
      await print('b')
      await print('c')
      await print('d')
    }
    test()

    假设await print('a')、await print('b')、await print('c')、await print('d')这四个操作并没有先后的逻辑关系,可以同时执行,那么按照上面的写法就会导致前一个执行完再执行下一个,整个执行过程中的等待时间会有4s,但是同时执行的等待时间就只有1s,这是在使用async异步函数会经常忽略的一个问题,那么怎么解决呢?介绍一个我经常使用的办法,看例子:

    function print(name) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(name)
          resolve('a')
        }, 1000)
      })
    }
    async function test () {
      Promise.all([print('a'), print('b'), print('c'), print('d')])
    }
    test()

    其实解决办法很简单就是通过Promise.all()方法,将所有异步操作作为参数数组传入,这样print('a')、print('b')、print('c')、print('d')这四个异步操作就可以并发执行了。

    总结

    这篇文章简单的介绍了一些常用的异步编程的方法,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,097
精华内容 1,638
关键字:

js异步编程