系统化学习前端之JavaScript(ES6:异步编程)
前言
JavaScript 异步编程这块东西比较多,涉及到宏任务和微任务,所以单开一个篇幅梳理一下。
同步和异步
同步和异步是一种宏观概念,具体表现在 JavaScript 中,是同步任务和异步任务,即同步函数和异步函数。
同步
同步指函数在 JavaScript 同步执行。
同步函数执行过程:A 函数进入函数调用栈,等待 A 函数执行完成后,B 函数进入函数调用栈,等待 B 函数执行完成后,C 函数...
异步
异步指函数在 JavaScript 异步执行。
异步函数执行过程:A 函数进入函数调用栈,判断 A 函数是否为同步函数,是则等待 A 函数执行完成后,B 函数进入函数调用栈,判断 B 函数是否为同步函数,否则添加至任务队列。
注意:任务队列根据 "执行者" 不同可以分为微任务队列和宏任务队列,具体可参考 V8 中异步回调。
JavaScript 执行流程
JavaScript 执行函数是通过调用栈进行的,按照全局作用域中的函数顺序(先进后出)依次进入函数调用栈执行。
JavaScript 函数执行顺序
-
函数调用栈依次执行全局作用域中的函数任务。
-
同步函数任务,函数调用栈会依次执行完成。
-
同步函数任务执行完成,函数调用栈会执行微任务队列中的微任务(微任务也是函数任务,只不过是回调函数)。
-
执行完微任务队列以后,函数调用栈会执行宏任务队列中的宏任务(宏任务也是回调函数)。
异步编程
JavaScript 是单线程语言,多任务执行必须借助异步编程来实现,而 JavaScript 对异步编程的实现就是回调函数。将一个任务拆分两个部分,执行函数执行一部分,回调函数执行另一部分。
回调函数:函数作为参数传入另一个函数,则该函数称为回调函数,被传参函数为执行函数。
回调地狱
了解了 JavaScript 函数的执行顺序:同步函数 --> 微任务 --> 宏任务。微任务和宏任务其实都是回调函数,那又如何保证回调函数能按照指定顺序执行呢?或者说,一个异步任务拆分成 A,B,C三个部分,该如何保证 A,B,C 顺序执行呢?
常规方法是在回调中调用下一个回调函数,保证顺序执行。如:
setTimeout(function() {
A()
setTimeout(function() {
B()
setTimeout(function () {
C()
}, 300)
}, 200)
}, 100)
上述代码可以保证 A
, B
, C
三个任务顺序执行,但是,如果存在多个任务呢?那就形成了多层回调函数嵌套而成的回调地狱。
回调地狱会带来阅读性差,难以维护等问题,因此,Promise 应运而生。
Promise
Promise 是允许将回调函数的嵌套,改成链式调用的方案。Promise 对象内部封装了处理异步操作的 API,可以将异步操作以同步操作的流程表达出来。
-
Promise 对象
实例化 Promise 对象
let promise = new Promise(function (resolve, reject) { // 异步任务的第一个部分操作 setTimeout(function () { let res = A() if(res == success) { resolve(successValue) } else { reject(errorValue) } }) })
Promise
是一个构造函数,接收一个回调函数,回调函数的参数是resolve
和reject
方法,这两个参数是内置方法,由 JavaScript 引擎提供。实例化过程中,参数回调函数会立即执行,同时执行异步任务第一部分
A()
操作,根据A()
返回结果:成功执行resolve
方法,失败则执行reject
方法。实例
promise
对象内部保存三个状态,分别是:pending
,fulfilled
和rejected
。根据实例化过程中,A()
返回结果不同则状态不同:若A()
返回失败或抛出错误,则promise
状态会由pending
变为rejected
,反之,则由pending
变为fulfilled
。实例
promise
对象内部也保存一个结果值,当fulfilled
状态时,会存在resolve
方法的参数值successValue
,当rejected
状态时,会保存reject
方法的参数值。-
pending
-->fulfilled
function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,1) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise)
-
pending
-->rejected
function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,2) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise)
-
-
Promise 原型方法
原型方法是实例可以直接调用的。
-
then
then
方法接收两个回调函数作为参数,第一个回调函数为成功回调函数;第二个回调函数为失败回调函数,可以缺省。promise
实例的状态为fulfilled
,则执行第一个回调函数,回调函数的参数为resolve
方法的参数,也是promise
的结果值。function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,1) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise) let promise1 = promise.then(function(res) { console.log(res) return 3 }) console.log(promise1)
promise
实例的状态为rejected
,则执行第二个回调函数,回调函数的参数为reject
方法的参数,同样是promise
的结果值。function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,2) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise) let promise1 = promise.then(function(res) { console.log(res) return 3 }, function(err) { console.log(err) return 0 // throw new Error('err') }) console.log(promise1)
注意:
a.
promise.then()
会返回一个promise
;b. 返回的
promise
的状态和结果值可以根据then
的回调函数返回值决定;c. 有返回值,则返回值作为
promise
的结果值;d. 无返回值,则
promise
的结果值为 undefined;e. 状态有无返回值均为
fulfilled
,只有当回调函数中抛出错误,状态才会变为rejected
。 -
catch
当
then
方法缺省第二个参数时,可以使用catch
方法,该方法接收一个回调函数,同then
方法第二个参数。当promise
的状态为rejected
或者异步操作中抛出错误均会被catch
方法捕获,回调函数可以获取错误信息。同样,catch
方法也返回一个promise
。function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,2) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise) let promise1 = promise.then(function(res) { console.log(res) return 3 }).catch(function(err) { console.log(err) return 0 }) console.log(promise1)
-
finally
无论
promise
的状态是fulfilled
还是rejected
都会执行finally
方法的回调函数,类似try...catch...finally
。function sum(x, y) { return x + y } let promise = new Promise(function(resolve, reject) { console.log('立即执行了回调函数') setTimeout(function () { let res = sum(1,2) if(res === 2) { resolve('success') } else { reject('fail') } }, 1000) }) console.log(promise) let promise1 = promise.then(function(res) { console.log(res) return 3 }).catch(function(err) { console.log(err) return 0 }).finally(function() { console.log('well done') }) console.log(promise1)
注意:
then
和catch
会返回一个promise
,因此promise
可以链式调用,如promise.then().then().then().catch().finally()
。同样,catch
置于链式调用的最后,可以捕获之前promise
或者then
返回的promise
的rejected
状态或抛出的异常,这种情况称之为异常穿透。 -
-
Promise 静态方法
-
all
Promise.all
接收多个promise
对象构成的数组,表示同时执行多个promise
对象中异步操作,返回一个promise
对象,返回promise
对象的状态和结果值由promise
数组决定。当多个
promise
都执行完成后,返回promise
均为fulfilled
时,则all
返回的promise
的状态为fulfilled
,结果值为参数数组中promise
结果值构成的数组。function sum(x, y) { return x + y } let p2 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,1) if(res > 1) { resolve(res) } else { reject('p2 fail') } }) }) let p3 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,2) if(res > 1) { resolve(res) } else { reject('p3 fail') } }) }) let p4 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,3) if(res > 1) { resolve(res) } else { reject('p4 fail') } }) }) let promise = Promise.all([p2,p3,p4]) console.log(promise)
当多个
promise
中有一个执行完成,并返回rejected
状态时,则all
返回当前promise
对象,即第一个执行完成且返回rejected
的promise
对象。function sum(x, y) { return x + y } let p0 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(0,0) if(res > 1) { resolve(res) } else { reject('p0 fail') } }, 200) }) let p1 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,0) if(res > 1) { resolve(res) } else { reject('p1 fail') } }, 100) }) let p2 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,1) if(res > 1) { resolve(res) } else { reject('p2 fail') } }) }) let promise = Promise.all([p0,p2,p1]) console.log(promise)
-
race
Promise.race()
接收多个promise
对象构成的数组,表示同时执行多个promise
对象中异步操作,返回一个promise
对象,返回promise
对象为多个promise
中第一个执行完成并返回的promise
对象。function sum(x, y) { return x + y } let p0 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(0,0) if(res > 1) { resolve(res) } else { reject('p0 fail') } }, 200) }) let p1 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,0) if(res > 1) { resolve(res) } else { reject('p1 fail') } }, 100) }) let p2 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,1) if(res > 1) { resolve(res) } else { reject('p2 fail') } }, 50) }) let promise = Promise.race([p0,p2,p1]) console.log(promise)
-
any
Promise.any
与Promise.all
相对应,接收多个promise
对象构成的数组,表示同时执行多个promise
对象中异步操作,返回一个promise
对象,返回promise
对象的状态和结果值由promise
数组决定。当多个
promise
都执行完成后,返回promise
均为rejected
时,则any
返回的promise
的状态为rejected
,结果值为参数数组中promise
结果值构成的数组。function sum(x, y) { return x + y } let p0 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(0,0) if(res > 1) { resolve(res) } else { reject('p0 fail') } }, 200) }) let p1 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,0) if(res > 1) { resolve(res) } else { reject('p1 fail') } }, 100) }) let promise = Promise.any([p0,p1]) console.log(promise)
当多个
promise
中有一个执行完成,并返回fulfilled
状态时,则any
返回当前promise
对象,即第一个执行完成且返回fulfilled
的promise
对象。function sum(x, y) { return x + y } let p0 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(0,0) if(res > 1) { resolve(res) } else { reject('p0 fail') } }, 200) }) let p1 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,0) if(res > 1) { resolve(res) } else { reject('p1 fail') } }, 100) }) let p2 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,1) if(res > 1) { resolve(res) } else { reject('p2 fail') } }, 300) }) let promise = Promise.any([p0,p2,p1]) console.log(promise)
-
allSettled
Promise.allSettled()
接收多个promise
对象构成的数组,表示同时执行多个promise
对象中异步操作,当所有异步操作均完成以后,返回一个promise
对象,返回promise
对象状态一直为fulfilled
,结果值为多个promise
状态结果对象组成的数组。function sum(x, y) { return x + y } let p0 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(0,0) if(res > 1) { resolve(res) } else { reject('p0 fail') } }, 200) }) let p1 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,0) if(res > 1) { resolve(res) } else { reject('p1 fail') } }, 100) }) let p2 = new Promise(function (resolve, reject) { setTimeout(function () { let res = sum(1,1) if(res > 1) { resolve(res) } else { reject('p2 fail') } }, 300) }) let promise = Promise.allSettled([p0,p2,p1]) console.log(promise)
-
resolve
Promise.resolve
接收一个参数,可以将参数转化为promise
对象并返回。当不传参时,返回一个
promise
对象,状态为fulfilled
,结果值为undefined
。当参数为
promise
对象,则直接返回参数promise
对象。当参数为 JavaScript 数据类型,则返回一个状态为
fulfilled
,结果值为参数的promise
对象。当参数为
thenable
对象,则返回一个promise
,并立即执行对象的then
方法。let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 });
-
reject
Promise.reject
接收一个参数,会返回一个状态为rejected
,结果值为参数的promise
对象,不传参,则结果值为undefiend
。
-
Generator
Generator 是 JavaScrip 异步编程的又一方案。Generator 函数是一个状态机,封装了多个内部状态,通过返回一个生成器对象来保存状态,利用 API 手动获取状态,异步读取结果。
-
生成器函数
Generator 函数又被称为生成器函数,是一种特殊的函数。相比于普通函数,主要标志有两点:
*
和yield
。function* gen() { yield 'generator start' yield 'generator' return 'generator end' } let g = gen() console.log(g.next()) // {value: 'generator start', done: false} console.log(g.next()) // {value: 'generator', done: false} console.log(g.next()) // {value: 'generator end', done: true} console.log(g.next()) // {value: undefined, done: true}
Generator 函数通过
yield
和return
关键字定义状态,调用 Generator 函数会返回一个可迭代的生成器对象。通过原型方法或者遍历可以读取生成器对象保存的状态。注意:
return
也可以定义状态,但一定是定义最后一个状态。yield
只能用于生成器函数,用于普通函数会抛出错误。 -
原型方法
-
next
g.next()
方法每调用一次可以读取一次生成器对象g
中的状态,当状态读取完毕,依旧调用会返回{value: undefined, done: true}
。g.next()
接收参数实际是向生成器函数传值,可以通过上一次的yield
的返回值获取数据。function* fun() { yield 'generator start' let res = yield 'generator' yield res return 'generator end' } let f = fun() console.log(f.next()) // {value: 'generator start', done: false} console.log(f.next()) // {value: 'generator', done: false} console.log(f.next('step three')) // {value: 'step three', done: false} console.log(f.next()) // {value: 'generator end', done: true}
-
return
g.return
方法调用是结束生成器对象g
的状态读取,不传参则读取结果为{value: undefined, done: true}
,传参则 value 为参数值function* fun() { yield 'generator start' yield 'generator' return 'generator end' } let f = fun() console.log(f.next()) // {value: 'generator start', done: false} console.log(f.return('intercept')) // {value: 'intercept', done: true} console.log(f.next()) // {value: undefined, done: true}
-
-
异步操作同步化
异步任务分为 A,B,C三个部分,顺序执行
A()
,B()
,C()
。function* gen() { yield A() yield B() yield C() } let g = gen() g.next() g.next() g.next()
-
co 模块
Generator 函数调用需要手动调用,如
g.next()
。co 模块可以自动化执行 Generator 函数。const co = require('co') function* gen() { yield 'generator' } co(gen)
async 和 await
async 和 await 是 Generator 函数的语法糖。但实际上功能和语义远强于 Generator 函数。
-
异步函数
异步函数是指使用
async
标记的函数,表示存在异步操作,await
后接异步操作的promise
对象,非promise
对象,会通过Promise.resolve
处理得到promise
对象。async function fun() { let res = await Promise.resolve(1) console.log(res) // 1 return 2 } let f = fun() console.log(f) // Promise<fulfilled, 2>
await
相当于promise.then
可以获取fulfilled
状态下promise
的结果值。注意:异步函数会返回一个
promise
对象,结果值为异步函数返回值,状态在异步函数抛出错误时为rejected
,反之为fulfilled
。 -
错误处理
await
只能获取fulfilled
状态下的结果值,无法拦截rejected
的异常。需要借助try...catch
进行错误处理。async function fun() { try { let res = await Promis.reject('fail') } catch(err) { console.log(err) // fail } } fun()
后记
ES6 拓展的异步编程处理方案,主要包括 Promise,Generator以及async,await,这些异步操作都是程序为执行者处理的,是微任务。而 JavaScript中通过浏览器线程处理的,如定时器,事件监听,网络请求等,是宏任务。
本文来自博客园,作者:深巷酒,转载请注明原文链接:https://www.cnblogs.com/huangminghua/p/17286017.html