自己动手写async/await

2022-12-19 程序员资讯 0 1,077
¥ 2.88B

包年VIP免费升级包年VIP

开通VIP尊享优惠特权
立即下载 升级会员

这几天在自学async/await,但仍旧无济于事。为的是更快地认知async/await,我下定决心他们同时实现两个莫雷县的async/await,以自学async/await组织工作基本原理。该工程建设名叫ToyAsync,库房门牌号如下表所示:

https://github.com/pandengyang/toyasync.git

function login(username, password, callback) { setTimeout(function () { if (username === kernelnewbies && password === 123456) { callback(null, 8ef9e41db21905efdefd45241466f9b3) } else{ callback(new Error(username or password error), null) } }, 3000) } function fetchProfile(token, callback) { setTimeout(function () { if (token === 8ef9e41db21905efdefd45241466f9b3) { callback(null, “{name:kernelnewbies,age:18}”) } else { callback(new Error(token error), null) } }, 3000) } login(kernelnewbies, 123456, function onLogin(error, token) { if (error) { console.log(error) return } console.log(token) fetchProfile(token, function onFetchProfile(error, profile) { if (error) { console.log(error) return } console.log(profile) }) })

这是典型的回调函数的写法。ES6中同时实现了Generator,使用Generator,开发者可以像写同步代码一样写异步代码。比如,上述的代码可被写为如下表所示方式:

var token = login(kernelnewbies, 123456) varprofile = fetchProfile(profile)

首先,使用Generator对之前的回调代码进行改造,代码如下表所示:

var g function* main() { var token var profile try { token = yieldlogin(// 1 kernelnewbies, 123456, function onLogin(error, data) { if (error) { g.throw(error) // 2.1 return} g.next(data)// 2.2 } ) console.log(token) profile = yield fetchProfile(token, function onFetchProfile(error, data) { if (error) { g.threw(error) return } g.next(data) }) console.log(profile) }catch (e) { // 3 console.log(e) } } g = main() g.next()

代码1处,login运行完后,让出执行权。yiled阻塞了Generator函数*main的运行,但是不会阻塞*main外代码的运行。

try { token = yield login( // 1

当login内部的定时器超时后,在login的回调函数中恢复*main的运行,并将结果传递给*main。当login成功时,通过g.next将结果传入*main(代码2.2处),当login失败时,通过g.throw将错误抛给*main(代码2.1处)。代码如下表所示:

function onLogin(error, data) { if (error) { g.throw(error) // 2.1 return } g.next(data)// 2.2 }

当login成功时,token被赋予通过g.next传入的值,代码如下表所示:

try { token = yield login( // 1

当login失败时,*main可捕获通过g.throw抛出的异常,代码如下表所示:

} catch (e) { // 3

fetchProfile的使用与login类似,此种方式需要在回调函数中编写调度Generator的代码,十分繁琐。

借助thunk函数,可以自动执行Generator函数。在js中,thunk函数可将多参数的函数转换为单参数的函数。举个例子,有如下表所示计算3个数乘积的函数,代码如下表所示:

function mul3(x, y, z) { returnx * y * z }

圆的周长的计算公式为:2 * π * 半径,利用thunk函数可从mul3生成两个用于计算圆周长的函数,示例如下表所示:

function ThunkMul3(x, y) { return function (radius) { return mul3(x, y, radius) } } var circleCircumference = ThunkMul3(2, Math.PI) console.log(circleCircumference(1)) console.log(circleCircumference(2))

为的是自动执行Generator函数,我们需要将login、fetchProfile转换成单参数的版本。之所以这样做是因为用户编写的异步函数的参数各不相同,但至少有两个回调函数参数,而且该回调函数通常用于接收结果并下定决心下一步的操作。

可以由执行器提供两个通用的回调函数next用于接收结果并调度Generator运行,因此,需要将用户函数thunk化为只接受两个回调函数参数的版本。

首先,编写两个通用的thunk化函数,代码如下表所示:

function thunkify(fn) { var args = Array.prototype.slice.call(arguments, 1) return function (cb) { args.push(cb) return fn.apply(null, args) } }

利用thunkify可将login、fetchProfile转换成单参数的版本,代码如下表所示:

var thunkLogin = thunkify(login, kernelnewbies, 123456) var thunkFetchProfile = thunkify(fetchProfile, token)

对于login来说,下面两种调用方法是等价的:

login(kernelnewbies, 123456, cb) thunkLogin(cb)

对于fetchProfile来说,下面两种调用方法是等价的:

fetchProfile(token, cb) thunkFetchProfile(cb)

使用执行器自动执行Generator函数的代码如下表所示:

function* main() { var token var profile try { token = yieldthunkify(login,kernelnewbies, 123456) // 3 console.log(token) profile = yieldthunkify(fetchProfile, token)console.log(profile) } catch (e) { // 5 console.log(e) } } function run(fn) { g = fn() function next(error, data) { if (error) { g.throw(error) // 2.1 return } var result = g.next(data) // 2.2 if(result.done) {return } result.value(next) // 4 } next(null, null) // 1 } run(main)

代码3处,thunkify运行完后,让出执行权,并返回login的thunk版。此时,代码2.1处接收到的返回值如下表所示:

{value: thunkLogin, done: false}

代码4处,执行thunkLogin,代码如下表所示:

result.value(next) // 4

该行等价于:

thunkLogin(next)

也就是说在run函数中真正执行了login函数。该行运行后,代码1处的next函数、run函数就退出了,继续执行后续代码。

从中我们可以看出js没有被阻塞,会继续执行run(main)后面的代码。但是*main却被阻塞了,等待异步执行的结果。

当login中的定时器超时后,代码4处,传入的回调函数next被调度运行:

result.value(next) // 4

next运行时,代码2.1或2.2处,login函数执行的结果通过g.next或g.throw传入到*main中,代码如下表所示:

if(error) { g.throw(error) // 2.1 return } var result = g.next(data) // 2.2

代码3处,login运行结果被赋值给token,代码如下表所示:

token = yield thunkify(login, kernelnewbies, 123456) // 3

代码5处,若login运行出错,*main可捕获login抛入的异常。

} catch (e) { // 5

fetchProfile的使用同login类似,通过此种方式可以同时实现Generator函数的自动执行。

上述场景也可使用Promise同时实现,代码如下表所示:

function login(username, password) { return new Promise(function (resolve, reject) { setTimeout(function () { if (username === kernelnewbies&& password ===123456) { resolve(8ef9e41db21905efdefd45241466f9b3) } else { reject(new Error(username or password error)) } }, 3000) }) } function fetchProfile(token) { return new Promise(function (resolve, reject) { setTimeout(function () { if (token === 8ef9e41db21905efdefd45241466f9b3) { resolve(“{name:kernelnewbies,age:18}”) } else { reject(new Error(token error)) } },3000) }) } login(kernelnewbies, 123456) .then(function fullfilled(value) { console.log(value)return fetchProfile(value) }) .then(function fullfilled(value) { console.log(value) }) .catch(function rejected(error) { console.log(error) })

使用Generator对Promise代码进行改造,代码如下表所示:

var g function* main() { var token var profile try { token = yield login(kernelnewbies, 123456).then( function fullfilled(data) { g.next(data) // 2.1 }, function rejected(error) { g.throw(error) // 2.2 } ) // 1 console.log(token) profile = yield fetchProfile(token).then( function fullfilled(data) { g.next(data) },function rejected(error) { g.throw(error) } ) console.log(profile) } catch(error) {// 3 console.log(error) } } g = main() g.next()

代码1处,login及then执行完毕后,让出执行权,代码如下表所示:

token =yield login(kernelnewbies, 123456).then() // 1

login是立即执行的,并返回两个Promise。当定时器超时后,该Promise决议并执行then注册的决议/拒绝函数。代码2.1或2.2处,在决议函数中将执行结果传回*main,代码如下表所示:

token = yield login(kernelnewbies, 123456).then( function fullfilled(data) { g.next(data) // 2.1 }, function rejected(error) { g.throw(error) // 2.2 } ) // 1

*main中,通过如下表所示代码接收login执行返回的结果:

token = yield login(kernelnewbies, 123456).then(

或捕获login抛出的异常:

} catch(error) {// 3

fetchProfile的使用与login类似,此种方式仍然需要调用then函数(虽然then链很短)。

同回调函数方式类似,可以编写两个基于Promise的Generator执行器来优化掉then函数,代码如下表所示:

functionmain() {   var token   var profile   try {     token = yield login(kernelnewbies123456) // 1     console.log(token)     profile = yield fetchProfile(token)     console.log(profile)   } catch(error) {// 6     console.log(error)   } } function run(fn{   var g = fn()   function next(error, data{     if(error) {       g.throw(error)// 2.1       return     }     var result = g.next(data) // 2.2     if (result.done) {       return}     result.value.then(function fullfilled(data{         next(null, data) // 4       },       function rejected(error{         next(error, null) // 5       }     ) // 3   }   next(nullnull) } run(main)

代码1处,login执行完后,通过yield向run返回两个Promise,并暂停*main的运行,代码如下表所示:

token = yield login(kernelnewbies123456) // 1

代码2.2处,run通过result接收该Promise,代码如下表所示:

var result = g.next(data)

此时,result的值如下表所示:

{value: loginPromise, done: false}

代码3处,在run中为该Promise注册决议/拒绝回调函数,代码如下表所示:

result.value.then( ) // 3

此时next(null, null)与run(main)运行完毕并退出。当login中的定时器超时后,loginPromise被决议,调用next函数,并将结果交由next函数处理:

result.value.then(   function fullfilled(data{     next(null, data) // 4   },   function rejected(error{     next(error, null) // 5   } ) // 3

代码2.1与2.2处,在next函数中将login运行结果传回*main函数,代码如下表所示:

if (error) {   g.throw(error) // 2.1   return } varresult = g.next(data) // 2.2

代码1与6处,在*main函数中接收login运行结果或捕获login抛出的异常,代码如下表所示:

try {   token = yield login(kernelnewbies123456) // 1catch (error) { // 6

fetchProfile的使用同login。

ES6为我们提供了Generator执行器的官方同时实现,这就是async/await。利用async/await同时实现上述功能,代码如下表所示:

async function main() { var token var profile try { token = await login(kernelnewbies, 123456) console.log(token) profile = await fetchProfile(token) console.log(profile) } catch(error) {console.log(error) } } main()

觉得不错,就请您转发

资源下载此资源下载价格为2.88B,包年VIP免费,请先
2405474279

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务