博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第64条:对异步循环使用递归
阅读量:5296 次
发布时间:2019-06-14

本文共 4799 字,大约阅读时间需要 15 分钟。

Item 64:   Use Recursion for Asynchronous Loops 

Consider a function that takes an array of URLs and tries to download one at a time until one succeeds. If the API were synchronous, it would be easy to implement with a loop: 

function  downloadOneSync(urls)  { for  (var  i  =  0,  n  =  urls.length;  i  <  n;  i++)  { try  { return  downloadSync(urls[i]); }  catch  (e)  {  } } throw  new  Error("all  downloads  failed");}

But this approach won’t work for downloadOneAsync, because we can’t suspend a loop and resume it in a callback. If we tried using a loop, it would initiate all of the downloads rather than waiting for one to continue before trying the next: 

function  downloadOneAsync(urls,  onsuccess,  onerror)  { for  (var  i  =  0,  n  =  urls.length;  i  <  n;  i++)  { downloadAsync(urls[i],  onsuccess,  function(error)  { //  ? }); //  loop  continues } throw  new  Error("all  downloads  failed");}

So we need to implement something that acts like a loop, but that doesn’t continue executing until we explicitly say so. The solution is to implement the loop as a function, so we can decide when to start each iteration: 

function  downloadOneAsync(urls,  onsuccess,  onfailure)  { var  n  =  urls.length; function  tryNextURL(i)  { if  (i  >=  n)  { onfailure("all  downloads  failed"); return; } downloadAsync(urls[i],  onsuccess,  function()  { tryNextURL(i  +  1); }); }  tryNextURL(0);}

The local tryNextURL function is recursive: Its implementation involves 

a call to itself. Now, in typical JavaScript environments, a recursive 
function that calls itself synchronously can fail after too many calls 
to itself. For example, this simple recursive function tries to call itself 
100,000  times, but in most JavaScript environments it fails with a 
runtime error: 

function  countdown(n)  { if  (n  ===  0)  { return  "done"; }  else  { return  countdown(n  -  1);}} countdown(100000);  //  error:  maximum  call  stack  size  exceeded

So  how  could  the  recursive  downloadOneAsync  be  safe  if  countdown explodes when n is too large? To answer this, let’s take a small detour and unpack the error message provided by countdown. 

JavaScript environments usually reserve a fixed amount of space in 

memory, known as the call stack, to keep track of what to do next after 
returning from function calls. Imagine executing this little program: 

function  negative(x)  { return  abs(x)  *  -1;}function  abs(x)  { return  Math.abs(x); } console.log(negative(42));

At the point in the application where Math.abs is called with the argument 42, there are several other function calls in progress, each waiting for another to return. Figure 7.2 illustrates the call stack at this point. At the point of each function call, the bullet symbol (•) depicts the  place  in  the  program  where  a  function  call  has  occurred  and where that call will return to when it finishes. Like the traditional stack data structure, this information follows a “last-in, first-out” protocol: The most recent function call that pushes information onto the stack (represented as the bottommost frame of the stack) will be the first to pop back off the stack. When Math.abs finishes, it returns to the abs function, which returns to the negative function, which in turn returns to the outermost script. 

When a program is in the middle of too many function calls, it can run 

out of stack space, resulting in a thrown exception. This condition is 
known as stack overflow. In our example, calling countdown(100000) 
requires  countdown  to  call  itself  100,000  times,  each  time  pushing 
another stack frame, as shown in Figure  7.3. The amount of space 
required to store so many stack frames exhausts the space allocated 
by most JavaScript environments, leading to a runtime error. 

Now take another look at downloadOneAsync. Unlike countdown, which 

can’t return until the recursive call returns, downloadOneAsync only 
calls  itself  from  within  an  asynchronous  callback.  Remember  that 
asynchronous  APIs  return  immediately—before  their  callbacks  are 
invoked. So downloadOneAsync returns, causing its stack frame to be 
popped off of the call stack, before any recursive call causes a new 
stack frame to be pushed back on the stack. (In fact, the callback is 
always invoked in a separate turn of the event loop, and each turn of 
the event loop invokes its event handler with the call stack initially 

 

empty.) So downloadOneAsync never starts eating up call stack space, no matter how many iterations it requires. 

 

Things to Remember 

✦ Loops cannot be asynchronous. 

✦ Use recursive functions to perform iterations in separate turns of the event loop. 

✦ Recursion performed in separate turns of the event loop does not overflow the call stack. 

文章来源于:Effective+Javascript编写高质量JavaScript代码的68个有效方法 英文版

转载于:https://www.cnblogs.com/hghrpg/p/4593757.html

你可能感兴趣的文章
python查询mangodb
查看>>
软件测试(基础理论一)摘
查看>>
consonant combination
查看>>
基于Flutter实现的仿开眼视频App
查看>>
析构器
查看>>
驱动的本质
查看>>
Swift的高级分享 - Swift中的逻辑控制器
查看>>
https通讯流程
查看>>
Swagger简单介绍
查看>>
C# 连接SQLServer数据库自动生成model类代码
查看>>
关于数据库分布式架构的一些想法。
查看>>
大白话讲解 BitSet
查看>>
sql语句中where与having的区别
查看>>
Python数据分析入门案例
查看>>
0x7fffffff的意思
查看>>
Java的值传递和引用传递
查看>>
vue-devtools 获取到 vuex store 和 Vue 实例的?
查看>>
Linux 中【./】和【/】和【.】之间有什么区别?
查看>>
内存地址对齐
查看>>
看门狗 (监控芯片)
查看>>