JS异步编程

199 阅读4分钟

同步

所谓同步代码就是当前执行代码会阻塞接下来的代码执行。

例如,执行代码A时,没有获得返回值之前,代码B无法执行。当代码A执行完成获得返回值之后,可以执行代码B。

异步

异步不阻塞代码执行。

例如,代码A执行异步过程调用发出后,不会立即获得返回结果,代码B可以继续执行。

异步编程发展

回调函数

  1. ajax 请求的回调
  2. 定时器中的回调
  3. 事件回调
  4. Nodejs 中的一些方法回调

最经典的问题:回调地狱

Promise

定义

Promise是一个对象,从中可以获取异步操作的消息

优点

解决回调地狱,避免了层层嵌套的回调函数

缺点

当链式调用过多时,可读性和维护性也很差

执行状态

  1. 待定(pending):初始状态,既没有被完成,也没有被拒绝
  2. 已完成(fulfilled):操作成功完成
  3. 已拒绝(rejected):操作失败

需要特别注意的是,状态改变不可逆转。

解决回调地狱的技术手段

  1. 回调函数延迟绑定
    回调函数不是直接声明的,而是通过 then 方法传入的,即延迟传入,这就是回调函数延迟绑定
  2. 返回值穿透
    then 中回调函数的传入值创建不同类型的 Promise,然后把返回的 Promise 穿透到外层,以供后续的调用
    步骤1,2结合,产生了链式调用的效果
  3. 错误冒泡
    前面产生的错误会一直向后传递,被最终的 catch 接收到,就不用频繁地检查错误了

静态方法

  1. all
    Promise.all(iterable),iterable是一个可迭代对象,如Array
    用于汇总多个promise结果,返回结果:
  • 当所有结果成功返回时按照请求顺序返回成功
  • 当其中有一个失败方法时,则进入失败方法

例如,页面需要同时发出请求进行页面渲染,就可以用 Promise.all 来实现

  1. allSettled
    类似Promise.all
    区别:执行完之后不会失败,拿到每个 Promise 的状态,而不管其是否处理成功
    返回一个数组,每个Promise执行的结果,有成功fulfilled有失败rejected
  2. any
    Promise.any(iterable),iterable是一个可迭代对象,如Array
    和all相反
    返回结果:
  • 只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态
  • 所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态
  1. race
    Promise.race(iterable),iterable是一个可迭代对象,如Array
    返回结果:
  • 只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态
  • 所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态
  • 率先改变的 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);
});

Generator

Generator 函数是异步任务的容器,需要暂停的地方,都用 yield 语法来标注

Generator 是一个带星号的“函数”,并非真正的函数

执行流程:

  1. Generator 调用,程序阻塞,不会执行任何代码

  2. .next(),程序继续执行,遇到yield关键词执行暂停

  3. 一直执行next方法,最后返回一个对象,其存在两个属性:value 和 done两个属性

  4. done 属性代表返回值以及是否完成

thunk函数

接收一定的参数,会生产出定制化的函数,最后使用定制化的函数去完成想要实现的功能

例如:

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 fs = require('fs')

const readFileThunk = (filename) => {
    return (callback) => {
        fs.readFile(filename, callback);
    }
}

const gen = function* () {
    const data1 = yield readFileThunk('./test.txt')
    console.log(111, data1.toString())
    const data2 = yield readFileThunk('./test2.txt')
    console.log(222, data2.toString())
}

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

let g = gen();
run(g);

输出结果:

async/await

ES7推出的语法糖,号称异步的最终最优解决方案。用同步方式写异步代码。

async返回Promise对象,await控制执行顺序

上面例子,使用async / await改造

const fs = require('fs')

const readFilePromise = (filename) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        })
    }).then(res => res);
}

const gen = async function () {
    const data1 = await readFilePromise('./test.txt')
    console.log(data1.toString())
    const data2 = await readFilePromise('./test2.txt')
    console.log(data2.toString())
}

gen()

输出结果一致