帮酷LOGO
0 0 评论
  • 显示原文与译文双语对照的内容
文章标签:JAVA  introduction  Javascript  INT  
:orange_book: a brief introduction to using Promises in JavaScript

  • 源代码名称:promise-cookbook
  • 源代码网址:http://www.github.com/mattdesl/promise-cookbook
  • promise-cookbook源代码文档
  • promise-cookbook源代码下载
  • Git URL:
    git://www.github.com/mattdesl/promise-cookbook.git
  • Git Clone代码到本地:
    git clone http://www.github.com/mattdesl/promise-cookbook
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/mattdesl/promise-cookbook
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
  • 承诺手册

    这是在JavaScript中使用承诺的简单介绍,主要针对前端开发人员。

    请参见这里的,用于中文翻译。

    电子邮件内容

    介绍

    Promise是一种编程结构,可以减少异步编程的一些困难。 使用承诺可以帮助生成更精简。更易于维护和更容易构建的代码。

    本课程主要集中于ES6承诺语法,但将使用 Bluebird,因为它在浏览器中提供了出色的错误处理。 语法需要像 browserify 或者 web pack那样的bundler。 有关CommonJS和browserify的介绍,请参见 jam3-lesson-module-basics

    问题

    为了演示,我们将在浏览器中加载图像问题。 下面显示了使用 node 样式( 错误第一个) 回调的实现:

    functionloadImage(url, callback) {
     var image =newImage();
     image.onload=function() {
     callback(null, image);
     };
     image.onerror=function() {
     callback(newError('Could not load image at '+ url));
     };
     image.src= url;
    }

    提示: 上面 是在npm上实现的,作为 img。

    加载单个图像相对简单,如下所示:

    loadImage('one.png', function(err, image) {
     if (err) throw err;
     console.log('Image loaded', image);
    });

    但是,随着我们的应用程序变得复杂,代码也会变得。 如果我们采用相同的方法,但加载三个图像,事情会变得有些笨拙:

    loadImage('one.png', function(err, image1) {
     if (err) throw err;
     loadImage('two.png', function(err, image2) {
     if (err) throw err;
     loadImage('three.png', function(err, image3) {
     if (err) throw err;
     var images = [image1, image2, image3];
     console.log('All images loaded', images);
     });
     });
    });

    这会创建一个函数的"圣诞树";并导致难以读取和维护的代码。 此外,如果希望图像并行加载,则需要一个更复杂的解决方案( )。

    异步

    围绕错误的第一次回调构建了许多抽象,有时称为 errbacks。"

    解决这里问题的一种方法是使用异步插件模块:

    var mapAsync =require('async').map;var urls = [ 'one.png', 'two.png' ];mapAsync(urls, loadImage, function(err, images) {
     if (err) throw err;
     console.log('All images loaded', images);
    });

    在npm上独立存在类似的抽象,例如:

    这种方法非常强大。 它非常适合小型模块,因为它不会引入额外的膨胀或者供应商锁定,并且不存在承诺的承诺的其他一些缺陷。

    然而,在较大的范围内,承诺可以在整个应用程序中提供一个统一且可以组合的结构。 他们还将为 ES7异步/等待奠定基础。

    承诺

    让我们实现 上面,并承诺我们的控制流程。 乍看起来,这似乎是更多的开销,但是好处很快就会得到解决。

    new Promise(function(resolve, reject) {.. . })

    below 是如何使用承诺实现图像加载功能的。 我们将它称为 loadImageAsync,以便与前面的示例区分开来。

    varPromise=require('bluebird')functionloadImageAsync(url) {
     returnnewPromise(function(resolve, reject) {
     var image =newImage();
     image.onload=function() {
     resolve(image);
     };
     image.onerror=function() {
     reject(newError('Could not load image at '+ url));
     };
     image.src= url;
     });
    }

    这个函数返回一个新的Promise 实例,如果负载成功,它将被解析为 image,如果加载成功,则返回 ,如果失败,则拒绝。 在我们的例子中,我们为承诺实现。

    对于这样的边缘情况,通常只需要 Promise 构造函数,这里我们将回调样式API转换为promise样式 API。 在许多情况下,最好使用 promisify 或者 denodeify 实用程序将 node 样式( 错误第一个) 函数转换为它们的Promise 对应函数。

    例如在前面的loadImage 函数中,上面 变得非常简洁:

    varPromise=require('bluebird');var loadImageAsync =Promise.promisify(loadImage);

    或者使用 img插件模块:

    varPromise=require('bluebird');var loadImage =require('img');var loadImageAsync =Promise.promisify(loadImage);

    如果你不使用蓝鸟,则可以使用 es6-denodeify

    .then(resolved, rejected)

    每个 Promise 实例在它的Prototype上都有一个 then() 方法。 这允许我们处理异步任务的结果。

    loadImageAsync('one.png')
    . then(function(image) {
     console.log('Image loaded', image);
     }, function(err) {
     console.error('Error loading image', err);
     });

    then 有两个函数,可以是 null,也可以是未定义的。 当promise成功时调用 resolved 回调,并将它的传递给已经解析的值( 在这种情况下,image )。 当promise失败时调用 rejected 回调,并将它传递给我们先前创建的Error 对象。

    .catch(err)

    promise也有 .catch(func) 来处理错误,这与 .then(null, func) 相同,但是提供了更清晰的。

    loadImageAsync('one.png')
    . catch(function(err) {
     console.error('Could not load image', err);
     });

    链接

    .then() 方法总是返回 Promise,这意味着它可以被链接。 上面 可以像这样写。 如果一个承诺被拒绝,那么下一个 catch() 或者 then(null, rejected) 将被调用。

    在下面的示例中,如果 loadImageAsync 方法被拒绝,则唯一的输出将是错误消息。

    loadImageAsync('one.png')
    . then(function(image) {
     console.log('Image loaded', image);
     return { width:image.width, height:image.height };
     })
    . then(function(size) {
     console.log('Image size:', size);
     })
    . catch(function(err) {
     console.error('Error in promise chain', err);
     });

    一般来说,你应该注意长承诺链。 它们可能很难维护,将任务分割成较小的命名函数会更好。

    解析值

    then()catch() 回调可以返回一个值,将它传递到链中的下一个方法。 例如这里我们将错误解析为默认图像:

    loadImageAsync('one.png')
    . catch(function(err) {
     console.warn(err.message);
     return notFoundImage;
     })
    . then(function(image) {
     console.log('Resolved image', image);
     });

    上面 代码将尝试加载 'one.png',但如果加载失败,则会回退到使用 notFoundImage

    最酷的是,你可以返回一个 Promise 实例,它将在下一个 .then() 被触发之前被解析。 这个承诺得到的价值也将被传递到下一个 .then()

    loadImageAsync('one.png')
    . catch(function(err) {
     console.warn(err.message);
     returnloadImageAsync('not-found.png');
     })
    . then(function(image) {
     console.log('Resolved image', image);
     })
    . catch(function(err) {
     console.error('Could not load any images', err);
     });

    上面 尝试加载 'one.png',但如果失败,则加载 'not-found.png'

    Promise.all()

    让我们回到加载多个图像的原始任务。

    方法接受值或者承诺的array,并返回一个只解析一次的新的Promise 所有都被解析。 这里我们使用 loadImageAsync 将每个URL映射到一个新的Promise,然后将这些承诺传递给 all()

    var urls = ['one.png', 'two.png', 'three.png'];var promises =urls.map(loadImageAsync);Promise.all(promises)
    . then(function(images) {
     console.log('All images loaded', images);
     })
    . catch(function(err) {
     console.error(err);
     });

    finally,事情开始变得更清晰了。

    buck

    你可能仍然想知道在 async 方法上有哪些改进。 真正的好处来自于跨应用程序的组合承诺。

    我们可以通过创建返回承诺的命名函数来"把钱传过去",让错误在上游。 上面 代码将如下所示:

    functionloadImages(urls) {
     var promises =urls.map(loadImageAsync);
     returnPromise.all(promises);
    }

    一个更复杂的示例可能如下所示:

    functiongetUserImages(user) {
     returnloadUserData(user)
    . then(function(userData) {
     returnloadImages(userData.imageUrls);
     });
    }functionshowUserImages(user) {
     returngetUserImages(user)
    . then(renderGallery)
    . catch(renderEmptyGallery);
    }showUserImages('mattdesl')
    . catch(function(err) {
     showError(err);
     });

    throw 和隐式捕获

    如果你的承诺链是你的承诺链,错误将被潜在的承诺实现捕获,并被当作一个对对你的承诺的调用。

    在下面的示例中,如果用户没有激活他们的帐户,则拒绝该帐户,showError 方法将被调用。

    loadUser()
    . then(function(user) {
     if (!user.activated) {
     thrownewError('user has not activated their account');
     }
     returnshowUserGallery(user);
     })
    . catch(function(err) {
     showError(err.message);
     });

    规范的这一部分常常被视为承诺的一个陷阱。 它通过组合语法错误。程序员错误( ) 来处理错误处理的语义,。 无效参数,并将连接错误与相同逻辑。

    在浏览器开发过程中,会导致错误: 可能会丢失调试器功能。堆栈跟踪和源映射详细信息。

    debugging

    对于许多开发人员来说,这足以避免promises错误first优先回调和类似于异步的错误。

    常见模式

    记忆

    即使异步任务完成后,我们也可以在promise上使用 .then()。 例如我们可以缓存第一个请求的结果,而不仅仅是请求同一个 'not-found.png' 映像,而只需要解析同一个 image 对象。

    var notFound;functiongetNotFoundImage() {
     if (notFound) {
     return notFound;
     }
     notFound =loadImageAsync('not-found.png');
     return notFound;
    }

    这对于服务器请求来说更有用,因为浏览器已经有一个缓存层来加载图像。

    Promise.resolve/Promise.reject

    Promise 类还提供了 resolvereject 方法。 当调用时,这些将返回一个新的承诺,以解析或者拒绝给它们的( 可选) 值。

    例如:

    var thumbnail =Promise.resolve(defaultThumbnail);//query the DBif (userLoggedIn) {
     thumbnail =loadUserThumbnail();
    }//add the image to the DOM when it's readythumbnail.then(function(image) {
     document.body.appendChild(image);
    });

    这里 loadUserThumbnail 返回一个解析为图像的Promise。 使用 Promise.resolve,即使它不涉及数据库查询,也可以将 thumbnail 视为相同的。

    处理用户错误

    返回承诺的函数应该总是不需要返回承诺,所以用户不需要将它们包装在一个 try/catch block 中。

    你应该返回一个带有错误的承诺,而不是在无效的用户参数上抛出错误。 Promise.reject() 在这里很方便。

    例如使用早期的 loadImageAsync 插件:

    functionloadImageAsync(url) {
     if (typeof url !=='string') {
     returnPromise.reject(newTypeError('must specify a string'));
     }
     returnnewPromise(function (resolve, reject) {
     /* async code */ });
    }

    或者,你可以使用 throw inside 承诺函数:

    functionloadImageAsync(url) {
     returnnewPromise(function (resolve, reject) {
     if (typeof url !=='string') {
     thrownewTypeError('must specify a string');
     }
     /* async code */ });
    }

    请参见这里的了解详细信息。

    ES2015中的Promise

    尽管本指南使用的是蓝鸟插件,但它应该在任何标准的承诺实现中工作。 例如使用 Babel

    其他一些实现:

    例如在 node/browserify中:

    // use native promise if it exists// otherwise fall back to polyfillvarPromise=global.Promise||require('es6-promise').Promise;

    陷阱

    除了在 throw 和隐式捕获插件中提到的问题之外,在选择承诺时还有其他一些问题需要牢记。 有些开发人员选择不使用承诺。

    在小型模块中使用

    在一个很小的情况下,承诺还不是很好,它是小型的自包含的 npm插件模块。

    • 取决于 bluebird 或者 es6-promise 是供应商锁的一种形式。 对于前端开发人员来说,这可能是一个问题,其中包大小是一个约束。
    • 由于本地 Promise ( ES2015 ) 构造函数在这些polyfills上创建对等依赖项,因此期望它也是一个。
    • 跨模块混合不同的promise实现可能会导致微妙的Bug 和调试 irks。

    在广泛支持本机承诺之前,使用节点样式回调和独立异步模块( ) 来控制控制流和较小的包大小通常是比较容易的。

    然后消费者可以用他们喜欢的实现来"promisify"。 例如在蓝鸟中使用 xhr插件模块可能如下所示:

    varPromise=require('bluebird')var xhrAsync =Promise.promisify(require('xhr'))

    复杂性

    承诺可以引入大量复杂和心理开销到代码库( 很明显,这本指南的) 中。 在实际项目中,开发人员通常会使用基于承诺的代码,而不完全了解承诺是如何工作的。

    有关这里问题的示例,请参见 Lawson"我们的承诺有问题"的Nolan

    锁定

    另一个挫折是,一旦你的代码库中的所有东西都在使用它们,承诺就会很好地工作。 在实践中,你可能会发现自己重构和"promisifying"很多代码,在获得承诺的好处之前。 这也意味着新的代码必须用记忆中的承诺写出来- 你现在已经被卡住了。

    进一步阅读

    为了全面列出承诺资源和小型模块以避免库锁定,请查看 Awesome Promise。

    许可证

    MIT,有关详细信息,请参阅 LICENSE.md



    文章标签:JAVA  INT  Javascript  introduction  

    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语