异步的一些理解

身为大自然的前端搬运工,为了能更好的搬运代码,咱们应该去理解前端中比较重要的异步调用部分。在说异步之前,我想先介绍generator函数,promise对象和async函数。

Generator函数

generator函数是一种异步编程的解决方案,语法会与传统函数有所不同。

在《ES6标准入门》中是这么介绍generator函数函数的,从语法上,首先可以把它理解成一个状态机,封装了多个内部状态。执行generator函数会返回一个遍历器对象,所以generator函数还是一个遍历器对象生成函数,可以通过它依次遍历generator函数内部的每一个状态。

形式上,generator函数有两个特征:

  1. function命令与函数名之间有一个星号。
  2. 函数体内部使用yield语句定义不同内部状态。
function* helloGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloGenerator(); //执行该函数
hw.next() 				 //{value: 'hello', done: false}
hw.next() 				 //{value: 'world', done: false}
hw.next() 				 //{value: 'ending', done: true}
hw.next() 				 //{value: 'undefine', done: true}

上面的函数就是一个例子helloGenerator函数内定义了3个状态:hello、world、return语句

generator函数的调用方法与普通函数类似,但是调用generator函数之后,只是返回一个指向内部状态的指针,通过每次调用next方法没事的指针向下一个状态移动,函数会执行一直到下个yield语句(或return语句)为止。就是说generator函数函数是分段执行的,yield语句是暂停执行标记,而next方法会恢复执行。而每次调用next都会返回yield后的语句的内容,若是函数会先执行(这就与异步相似),然后再返回函数执行的返回结果。

接下来说下next的运行逻辑:

  1. 遇到yield语句就暂停执行后面的操作,并紧跟在yield后面的表达式的值作为返回的对象的value属性值
  2. 下一次调用next方法时再继续往下执行,知道遇到下一条yield
  3. 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式值作为返回的对象的value属性值
  4. 如果没有return语句,也会执行但是返回的value里面是undefined

需要注意的是只有当我们调用next才会执行后续语句,所以很大程度上来说,generator函数的后续操作,都需要人为手动的控制,但是我们会在后续的讨论中说道co模块和thunk函数,关于自启动的说明。

但是我们通过向next传递一个参数这样子它会被当做上一条yield语句的返回值。我们要注意不能在普通函数中使用yield语句,不然会报错。而且yield语句如果用在一个表达式中,必须放在圆括号里面。

function* f() {
  for (let i = 0; true; i++) {
    let result = yield i;
    console.log('result:' + result);
    if (result) {
      i = -1;
    }
  }
}

var g = f();
g.next()  // {value: 0, done: false} 
// result:undefined

g.next()  // {value: 1, done: false}
// result:undefined

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

Promise对象

关于peomise对象,一开始的时候,我写下的几个函数,大家可以理解一下。

//实例一
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = true;
    if (x) {
       resolve();
    } else {
       reject();
    }
})
promise.then(function () {
    if (s) {
                
    }
    console.log('success');
}, function () {
    console.log('error');
})

//实例二
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = true;
    if (x) {
       resolve();
    } else {
       reject();
    }
})
promise.then(function () {
    if (s) {
                
    }
    console.log('success');
}).catch(function () {
    console.log('error');
})

//实例三
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = true;
    if (x) {
       resolve();
    } else {
       reject();
    }
})
promise.then(function () {
    console.log('success');
}, function () {
    console.log('error');
})

//实例四
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = false;
    if (x) {
       resolve();
    } else {
       reject();
    }
})
promise.then(function () {
    console.log('success');
}, function () {
    console.log('error');
})

//实例五
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    if (x) {
       resolve();
    } else {
       reject();
    }
})
promise.then(function () {
    console.log('success');
}, function () {
    console.log('error');
})

//实例六
let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = false;
    if (x) {
       resolve();
    } else {
       reject();
    }
})
    promise.then(function () {
        console.log('success1');
    }).then(function () {
        console.log('success2');
    }).catch(function () {
        console.log('error');
    })

//实例七
    let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = false;
    if (x) {
       resolve();
    } else {
       reject();
    }
    })
    promise.then(function () {
        console.log('success1');
    }).then(function () {
        console.log('success2');
    }, function () {
        console.log('error2');
    })
 
//实例八
    let promise = new Promise(function (resolve, reject) {
    console.log('in');
    let x = false;
    if (x) {
       resolve();
    } else {
       reject();
    }
    })
    promise.then(function () {
        console.log('success1');
    }, function () {
        console.log('error1');
    }).then(function () {
        console.log('success2');
    }, function () {
        console.log('error2');
    })
    //in
    //error1
    //success2

在上述中一共有8个实例,第一个和第二个很类似,但是第一个会在实例浏览器中报错,而第二个不会,因为catch会捕获报错。虽然在promise对象中我们可以通过操作去决定我们到底执行resolved状态还是rejected状态,但是这是相对的,使用then的第二个参数和使用catch是一样,都会被当做rejected状态,只要其中有个所以为了代码的健壮性,我更加建议使用catch。

但是看到第8个实例它的输出,你会发现,好像和预想的不一样,所以这就是要注意的地方,then和catch返回的都是promise对象,所以可以在后续中执行resolved状态。在异步操作中一定要记住这一点。

promise对象的错误是有冒泡性质的,会一直向后传递,知道被捕获为止,也就是说,错误总是会被下一个catch语句或者then的第二个参数捕获。这也就解释实例八的结果。在promise对象抛出的错误会一直冒泡到最外成,如果没有捕获,但是在谷歌浏览器中会抛出到最外层,只是不会影响后续操作。

我们需要理解当调用 Promise 的 then(…) 会自动创建一个新的 Promise 从调用返回,而其中的promise是怎么产生的。

promise.resolve()会返回一个新的promise对象,且其状态为resolved。

当调用promise.resolve的时候会对对第一个参数转换为一个promise,当参数不是promise的值时,会将其展开提取一个非类promise的最终值。与then类似,如果调用的是then通过return返回一个值,那么该值也会类似将其展开转换为promise

Promise.resolve('1').then((v) => {console.log(v)}) //1

new Promise(function(resolve, reject) {
    resolve(1);
}).then((v) => {
    console.log(v);
    return v * 2;
}).then((v) => {
    console.log(v);
})  
// 1
// 2

//实例十
new Promise(function(resolve, reject) {
    resolve(1);
}).then((v) => {
    console.log(v);
    return v * 2;
}).then((v) => {
    console.log(v);
})
Promise.resolve('3').then((v) => {console.log(v)})
//1
//3
//2

看上述实例十,我们可以知道通过return可以将返回值当做参数传递给下一个,否则的会是undefined,而在《你不知道的JS中卷》中:《《我们 使用了立即返回 return 语句,这会立即完成链接的 promise》》这句话,通过上述的例子好像是有点错误的,其实并没有立即完成链接的promise但是完成了特定的then。

async函数

要记住async函数是generator函数的语法糖。

  1. 具有内置执行器。
  2. 能自动执行,输出最后结果。
  3. 更好的语法。
  4. 更广的适用性。
  5. 返回值为Promise

这时候我给出一个实例

    function f1() {
        console.log('1');
    }
    function f2() {
        console.log('2');
    }
    function f3() {
        console.log('3');
    }
    async function fn() {
        await f1();
        await f2();
        await f3();
        return 1;
    }
    let g = fn();
    console.log(g);

这个实例的执行结果是怎么样的,可以吧它复制到控制面板中执行,你会发现输出的顺序是1,promise对象,2,3。这是为什么,为什么1之后是promise,展开promise对象,会发现里面的promiseValue值是1,那这个值是不是和yield一样返回的是后面表达式的值呢,然后选择吧await f1();注释掉,重新执行就发现其实promise的promiseValue值没有改变,所以这不是和yield一样但是不可否认的是会返回一个Promise值。

但是为什么是promise对象之后才是2,3呢,不是先1,2,3才promise对象,因为async就是一个异步函数,我们可以这么理解,当我们fn()的时候,会执行到第一个await后面的语句,然后停止执行,也就是输出了1,然后发现函数外还有代码需要执行,就会暂停,执行函数外的后续代码,等到系统空闲,就会继续执行函数内后续的代码。

所以结果就是1,promise对象,2,3。

异步操作

其实“异步”在我看来就是一部分先执行,一部分后执行。而在js中最简单的异步就是ajax中的回调函数,回调函数就是把任务的第二个阶段单独写在一个函数中,当做参数传递进去也就是callback参数。而异步的核心就是现在运行的代码和未来运行的关系。程序中将来执行的部分并不一定在现在运行的部分执行完之后就立即执行。无法完成的任务就会异步完成。比如在一开始的页面请求中,为避免页面的卡死,会将向后台请求的代码进行异步处理,一般利用ajax就是回调。而真正的异步代码,直到es6才开始内建起来(Promise对象)。

JS的引擎并不是独立运行的,它运行在宿主换将中(一般指浏览器)。这些环境都有一个共同点,它们都提供了一种机制来处理程序中多个快的执行,且执行每块时调用JS引擎,这种机制就是事件循环。

可以参考这个图:

Image text

并行计算最常见的工具就是进程和线程。进程和线程独立运行,并可能同时运行:在不同 的处理器,甚至不同的计算机上,但多个线程能够共享单个进程的内存。但是事件循环吧自身的工作分成一个个任务,内存不会共享。但是通过分立线程中彼此合作的事件循环,可以共享。

一个任务可能引起更多任务被添加到同一个队列末尾。所以,理论上说,任务循环(job loop)可能无限循环(一个任务总是添加另一个任务,以此类推),进而导致程序的 饿死,无法转移到下一个事件循环tick。一旦有事件需要运行,事件循环就会运行,直到队列清空。事件循环的每一轮称为一个 tick。用户交互、IO 和定时器会向事件队列中加入事件。任意时刻,一次只能从队列中处理一个事件。执行事件的时候,可能直接或间接地引发一 个或多个后续事件。

但是JS是单线程运行特性。不会进行并行执行除非使用web worker。单线程事件循环是并发的一种形式。

猜你喜欢

转载自blog.csdn.net/weixin_37749567/article/details/86771971
今日推荐