第64条:对异步循环使用递归

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个有效方法 英文版

时间: 2024-10-29 19:11:30

第64条:对异步循环使用递归的相关文章

JS对异步循环使用递归

场景:有一个函数接收一个URL的数组,要求依次下载每个文件直到所有文件被成功下载. 如果API是同步的,很容易使用一个循环来实现 var downloadAllSync = function(urls) { urls.foreach(function(url){ try{ return downloadOneSync(url); }catch(e) {} }); throw new Error('all downloads failed'); } 但是用这种方式实现downloadAllAsyn

3循环与递归

循环设计中要注意算法的效率: 循环体的特点是:"以不变应万变". 所谓"不变"是指循环体内运算的表现形式是不变的,而每次具体的执行内容却是不尽相同的.在循环体内用不变的运算表现形式去描述各种相似的重复运算. [例1]求1/1!-1/3!+1/5!-1/7!+-+(-1)n+1/(2n-1)! 分析:此问题中既有累加又有累乘,准确地说累加的对象是累乘的结果. 数学模型1:Sn=Sn-1+(-1)n+1/(2n-1)! 算法设计1:多数初学者会直接利用题目中累加项通式,

二分查找【循环和递归】

对一个数组a,在区间下标[x,y]寻找p是否存在,存在则返回下标,否则返回-1. 循环和递归实现:(练习用的程序) 1 #include <stdio.h> 2 #include<stdlib.h> 3 #include<time.h> 4 5 int binSerrch(int a[],int x,int y,int p);//在非递减数组a的x到y区间寻找p,假如存在则返回p的下标,否则返回-1 6 int binSerrch2(int a[],int x,int

二叉树的三种遍历方式的循环和递归的实现方式

///////////////////头文件:BST.h//////////////////////// #ifndef BST_H #define BST_H #include "StdAfx.h" #include<iostream> #include<stack> template<typename DataType> class BST { public: class Node { public: Node(int data=0):m_dat

window 7 64位下可运行的递归删除注册表程序

为了完成一个简单的注册表删除程序,由于我本身是win7 64位的OS,写了以下代码来递归删除注册表键值,程序代码如下: #include <tchar.h> #include <afx.h> #define KEY_WOW64_64KEY 256 long DeleteSubKeyTree(HKEY hKey, LPCTSTR lpSubKey) { LONG lResult; HKEY hSubKey; DWORD dwIndex, cbName; char szSubKey[5

Atitit &#160;循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate).

Atitit  循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate). 1.1. 循环算是最基础的概念, 凡是重复执行一段代码, 都可以称之为循环. 大部分的递归, 遍历, 迭代, 都是循环.1 1.2. 递归的定义是, 根据一种(几种)基本情况定义的算法, 其他复杂情况都可以被逐步还原为基本情况.1 1.3. 递归的基本概念和特点1 1.4. 迭代(数学): 在循环的基础上, 每一次循环, 都比上一次更为接近结果.2 1.5. 编程语言中的循环

算法学习之循环和递归

[摘要] 大家都知道递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的.原理上讲,所有递归都是可以消除的,代价就是可能自己要维护一个栈.而且我个人认为,很多情况下用递归还是必要的,它往往能把复杂问题分解成更为简单的步骤,而且很能反映问题的本质.循环和递归其实存在一定的联系.  1)求和递归函数 我们可以举一个循环的例子,前面我们说过,如果编写一个1到n的求和函数怎么写呢,你可能会这么写: int calculate(int m) { i

一步一步写算法(之循环和递归)

原文:一步一步写算法(之循环和递归) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 其实编程的朋友知道,不管学什么语言,循环和递归是两个必须学习的内容.当然,如果循环还好理解一点,那么递归却没有那么简单.我们曾经对递归讳莫如深,但是我想告诉大家的是,递归其实没有那么可怕.所谓的递归就是函数自己调用自己而已,循环本质上也是一种递归.  1)求和递归函数 我们可以举一个循环的例子,前面我们说过,如果编写一个1到n的求和函数怎么写呢,你可能会

codevs http://www.codevs.cn/problem/?problemset_id=1 循环、递归、stl复习题

12.10高一练习题 1.要求: 这周回顾复习的内容是循环.递归.stl. 不要因为题目简单就放弃不做,现在就是练习基础. 2.练习题: (1)循环   题目解析与代码见随笔分类  NOI题库 http://noi.openjudge.cn/ch0106/    10-15题 http://noi.openjudge.cn/ch0105/     37-45题 http://noi.openjudge.cn/ch0107/     28-35题 (2)递归   题目解析与代码见随笔分类 递归 h