JavaScript异步编程

异步编程

概述

JS执行环境中负责执行代码的线程只有一个,同一时间只能执行一个任务,为解决耗时任务阻断代码执行问题,JavaScript将任务的执行模式分为两种:

  • 同步模式(Synchronous)
  • 异步模式(Asynchronous)
同步模式

同步模式指的是代码当中的任务将会按照代码编写顺序依次执行,后一个任务必须等待前一个任务执行完毕才会执行。

console.log('first');

const fun2 = () => {
    console.log('second');
}

const fun3 = () => {
    fun2();
    console.log('third');
}

fun3();
console.log('end');

//结果是
=> first 
=> second
=> third
=> end

这里的代码是一步接一步往下执行

弊端:当代码中存在耗时较长的任务时,将会阻塞代码的执行,此时可以使用异步模式来解决这一问题。

异步模式

异步模式指代码执行过程中,不会等待一个任务执行完成再执行另一个任务,任务开启过后就立即往后执行下一个任务,耗时任务的后续逻辑一般会通过回调函数的方式定义,当耗时任务完成后就会自动执行回调函数。

console.log('begin');

setTimeout(() => {
  console.log('time1');
}, 1800);

setTimeout(() => {
  console.log('time2');
  setTimeout(() => {
    console.log('time2 inner');
  }, 800);
}, 1000);

console.log('end');

//结果是
=> begin 
=> end
=> time2
=> time1
=> time2 inner
Promise
概述

Promise是异步编程的一种解决方案,Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise状态:

  • Pending:等待(进行中)

  • Fulfilled:成功(已完成)

  • Rejected:失败

Promise特性:

  1. Promise的状态不受外界影响,只有异步操作的结果可以决定当前状态
  2. Promise的状态只能变更一次,一旦改变,状态就凝固了,不会再变,任何时候都可以得到这个结果。Promise的状态,只能由 Pending -> Fulfilled 或者从 Pending -> Rejected。Promise中使用resolve()reject()函数来改变状态。

Promise优缺点:

  • 优点:通过链式调用,让异步代码可以以同步操作的流程表示出来。解决了代码深层嵌套回调地狱的问题,使代码更加整洁更好维护

  • 缺点:

    1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
    2. 如果不设置回调函数(未定义catch捕获异常),Promise内部抛出的错误,不会反应到外部。
    3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise基本使用
function ajax(url) {
    return new Promise((resolve, reject) => {
        const 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/user.json')
    .then(res => {
        // 成功
    })
    .catch(err => {
        // 失败
    })
Promise静态方法
  1. Promise.resolve()

    此方法有一个可选的参数,参数的类型会影响它的返回值,具体可分为三种情况(如下所列),其中有两种情况会创建一个新的已处理的Promise实例,还有一种情况会返回这个参数。

    (1)当参数为空或非thenable时,返回一个新的状态为fulfilled的Promise。

    (2)当参数为thenable时,返回一个新的Promise,而它的状态由自身的then()方法控制

    (3)当参数为Promise时,将不做修改,直接返回这个Promise。

    // Promise中的静态方法 Promise.resolve
    
    // 1、参数为空  返回状态为fulfilled的新Promise,回调中接收传入参数
    Promise.resolve().then(res => {
        console.log(res); //undefined
    })
    // 2、参数为非thenable的值  返回状态为fulfilled的新Promise,回调中接收传入参数
    Promise.resolve([1, 2, 3]).then(res => {
        console.log(res); //[1,2,3]
    })
    // 3、参数为thenable  根据thenable内部返回值决定Promise状态,并返回结果
    Promise.resolve({
        then(resolve, reject) {
            let x=Math.random()*10
            x>5?resolve('thenable resolved'):reject(new Error('thenable rejected'))
        }
    }).then(res => {
        console.log('res===>',res);
    }).catch(err=>{
        console.log('err===>',err);
    })
    // 4、参数为Promise 直接返回该Promise
    Promise.resolve(new Promise((resolve,reject)=>{
        resolve('Get Promise')
    })).then(res=>{
        console.log(res);   //Get Promise
    })
    
  2. Promise.reject()

    此方法能接收一个参数,表示拒绝理由,它的返回值是一个新的已拒绝的Promise实例。与Promise.resolve()不同,Promise.reject()中所有类型的参数都会原封不动的传递给后续的已拒绝的回调函数。

    // Promise中的静态方法 Promise.reject
    
    Promise.reject().catch(err => {
        console.log(err); //undefined
    })
    
    Promise.reject([1, 2, 3]).catch(err => {
        console.log(err); //[1,2,3]
    })
    
    let thenable = {
        then(resolve, reject) {
            resolve('thenable resolved')
        }
    }
    Promise.reject(thenable)
        .then(res => {
            console.log('res==>', res); //不输出,reject返回的promise状态为rejected
        })
        .catch(err => {
            console.log('err==>', err); //err==> { then: [Function: then] } 
        })
    
    let p1 = new Promise((resolve, reject) => {
        resolve('Get Promise')
    })
    Promise.reject(p1)
        .catch(err => {
            console.log(err); //Promise { 'Get Promise' }
            console.log(err===p1); //true
        })
    
  3. Promise.all()

    此方法和接下来要讲解的Promise.race()都可用来监控多个Promise,当它们的状态发生变化时,这两个方法会给出不同的处理方式。

    Promise.all()能接收一个可迭代对象,其中可迭代对象中的成员必须是Promise,如果是字符串、thenable等非Promise的值,那么会自动调用Promise.resolve()转换成Promise。Promise.all()的返回值是一个新的Promise实例,当参数中的成员为空时,其状态为fulfilled;而当参数不为空时,其状态由可迭代对象中的成员决定,具体分为两种情况。

    (1)当可迭代对象中的所有成员都是已完成的Promise时,新的Promise的状态为fulfilled。而各个成员的决议结果会组成一个数组,传递给后续的已完成的回调函数。

    (2)当可迭代对象中的成员有一个是已拒绝的Promise时,新的Promise的状态为rejected。并且只会处理到这个已拒绝的成员,接下来的成员都会被忽略,其决议结果会传递给后续的已拒绝的回调函数。

    // Promise中的静态方法 Promise.all
    let p1 = Promise.resolve()
    let p2 = 'resolved'
    let p3 = Promise.reject('rejected')
    
    // 所有成员均为fulfilled,返回fulfilled,将结果数组传递给成功回调
    Promise.all([p1, p2])
        .then(res => {
            console.log('res', res);    //res [ undefined, 'resolved' ]
        })
        .catch(err => {
            console.log('err', err);
        })
        
    // 成员中存在rejected,返回rejected,将失败原因传递给失败回调
    Promise.all([p1, p3])
    .then(res => {
        console.log('res', res);
    })
    .catch(err => {
        console.log('err', err);    //err rejected
    })
    
  4. Promise.race()

    (1)能接收一个可迭代对象,如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

    (2)可迭代对象成员必须是Promise,非Promise的值,会自动调用Promise.resolve()转换成Promise。

    (3)返回值是一个新的Promise实例。

    ​ 返回的新的Promise实例的状态与方法的参数有关,当参数的成员为空时,其状态为pending,则返回的 promise 将永远等待;当参数不为空时,其状态是最先被处理的成员的状态,并且此成员的决议结果会传递给后续相应的回调函数,如下代码所示。

    // Promise中的静态方法 Promise.race
    
    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, "one");
    });
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 100, "two");
    });
    
    Promise.race([p1, p2]).then(function (value) {
        console.log(value); // "two"
        // 两个都完成,但 p2 更快
    });
    
    var p3 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 100, "three");
    });
    var p4 = new Promise(function (resolve, reject) {
        setTimeout(reject, 500, "four");
    });
    
    Promise.race([p3, p4]).then(function (value) {
        console.log(value); // "three"
        // p3 更快,所以它完成了
    }, function (reason) {
        // 未被调用
    });
    
    var p5 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, "five");
    });
    var p6 = new Promise(function (resolve, reject) {
        setTimeout(reject, 100, "six");
    });
    
    Promise.race([p5, p6]).then(function (value) {
        // 未被调用
    }, function (reason) {
        console.log(reason); // "six"
        // p6 更快,所以它失败了
    });
    
Generator
概述

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

Generator基本使用
function* gen(){
    yield 1;
    yield 2;
    return 'end';
}
const g=gen()
console.log(g.next())	//{value:1,done:false}
console.log(g.next())	//{value:2,done:false}
console.log(g.next())	//{value:'end',done:true}
console.log(g.next())	//{value:undefined,done:true}

上面代码定义了一个 Generator 函数gen,它内部有两个yield表达式(12),即该函数有三个状态:1,2和 return 语句(结束执行)。

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。

必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

yield表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

function* gen() {
  yield  123 + 456;
}
const g=gen()
console.log(g.next())	//{value: 579, done: false}

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

上面代码先定义了一个可以无限运行的 Generator 函数f,如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值