[关闭]
@duanyubin 2016-02-28T15:40:20.000000Z 字数 5562 阅读 349

We have a problem with promises

javascript


Background

javascript执行在一个线程上,如果同步执行,某些耗时较长的任务会阻塞线程,导致页面停止响应。
所以将这些耗时的任务交由系统其它线程处理,而这些任务完成后,会已事件的方式通知js线程。

Problems

Callback hell
回调嵌套,导致可能出现下列的问题。

  1. var MongoClient = require('mongodb').MongoClient,
  2. test = require('assert');
  3. MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
  4. // Add a user to the database
  5. db.addUser('user', 'name').then(function(result) {
  6. // Authenticate
  7. db.authenticate('user', 'name').then(function(result) {
  8. test.equal(true, result);
  9. // Logout the db
  10. db.logout().then(function(result) {
  11. test.equal(true, result);
  12. // Remove the user from the db
  13. db.removeUser('user').then(function(result) {
  14. // Authenticate
  15. db.authenticate('user', 'name').then(function(result) {
  16. test.equal(false, result);
  17. db.close();
  18. }).catch(function(err) {
  19. db.close();
  20. });
  21. });
  22. });
  23. });
  24. });
  25. });

Solution

  1. EventProxy
  2. Async
  3. q || jquery.deferred
  4. Promise(ES6) || Bluebird
  5. Async-await(ES7)

Promise

We have a problem with promises
CAUTION: Support via polyfill

Basic Usage

  1. function timeout(duration = 0) {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(resolve, duration);
  4. })
  5. }
  6. var p = timeout(1000).then(() => {
  7. return timeout(2000);
  8. }).then(() => {
  9. throw new Error("hmm");
  10. }).catch(err => {
  11. return Promise.all([timeout(100), timeout(200)]);
  12. })

Q: What is the difference between these four promises?

  1. doSomething().then(function () {
  2. return doSomethingElse();
  3. });
  4. doSomething().then(function () {
  5. doSomethingElse();
  6. });
  7. doSomething().then(doSomethingElse());
  8. doSomething().then(doSomethingElse);

注:下文中promise指的是Promise实例。

错误1. 金字塔式调用

如:

  1. remotedb.allDocs({
  2. include_docs: true,
  3. attachments: true
  4. }).then(function (result) {
  5. var docs = result.rows;
  6. docs.forEach(function(element) {
  7. localdb.put(element.doc).then(function(response) {
  8. alert("Pulled doc with id " + element.doc._id + " and added to local db.");
  9. }).catch(function (err) {
  10. if (err.status == 409) {
  11. localdb.get(element.doc._id).then(function (resp) {
  12. localdb.remove(resp._id, resp._rev).then(function (resp) {
  13. // et cetera...

更好的方式:

  1. remotedb.allDocs(...).then(function (resultOfAllDocs) {
  2. return localdb.put(...);
  3. }).then(function (resultOfPut) {
  4. return localdb.get(...);
  5. }).then(function (resultOfGet) {
  6. return localdb.put(...);
  7. }).catch(function (err) {
  8. console.log(err);
  9. });

组合式Promise, then的回调函数中,return 另外一个promise. 在下一个then回调中,前一个异步任务是已完成状态。

错误2.如何配合使用forEach

错误用法:

  1. // I want to remove() all docs
  2. db.allDocs({include_docs: true}).then(function (result) {
  3. result.rows.forEach(function (row) {
  4. db.remove(row.doc);
  5. });
  6. }).then(function () {
  7. // I naively believe all docs have been removed() now!
  8. });

这段代码的问题是:第一个函数返回的是undefined, 意味着第二个函数并不会等待db.remove()执行完毕才执行。
解决方案: Promise.all()

  1. db.allDocs({include_docs: true}).then(function (result) {
  2. return Promise.all(result.rows.map(function (row) {
  3. return db.remove(row.doc);
  4. }));
  5. }).then(function (arrayOfResults) {
  6. // All docs have really been removed() now!
  7. });

Promise.all()接受Promise数组作为参数,返回另一个promise,等待所有元素都执行完毕后才resolve.
另外,只要有一个子元素reject,返回的promise就是reject状态。

错误3.忘记加catch()

这将导致错误不能被捕捉,所以尽量加一个:

  1. somePromise().then(function () {
  2. return anotherPromise();
  3. }).then(function () {
  4. return yetAnotherPromise();
  5. }).catch(console.log.bind(console));

错误4.使用deferred

anti-pattern

错误5.没有return一个promise

  1. somePromise().then(function () {
  2. someOtherPromise();
  3. }).then(function () {
  4. // 希望someOtherPromise已经为resolve状态
  5. // 实际上并没有
  6. });

在每个then方法的函数中:
1. return另一个promise
2. return同步的值,或者undefined(尽量保证每个then方法都有return)
3. throw一个异常

  1. getUserByName('nolan').then(function (user) {
  2. if (user.isLoggedOut()) {
  3. throw new Error('user logged out!'); // throwing a synchronous error!
  4. }
  5. if (inMemoryCache[user.id]) {
  6. return inMemoryCache[user.id]; // returning a synchronous value!
  7. }
  8. return getUserAccountById(user.id); // returning a promise!
  9. }).then(function (userAccount) {
  10. // I got a user account!
  11. }).catch(function (err) {
  12. // Boo, I got an error!
  13. });

理解了上述这些,就理解了Promise

错误6. 不知道Promise.resolve()

不推荐写法:

  1. new Promise(function (resolve, reject) {
  2. resolve(someSynchronousValue);
  3. }).then(/* ... */);

推荐写法:

  1. Promise.resolve(someSynchronousValue).then(/* ... */);
  2. // 同样的
  3. Promise.reject(new Error('some awful error'));

错误7. catch并不完全等于 then(null, ...)

一般的下列二者是等价的:

  1. somePromise().catch(function (err) {
  2. // handle error
  3. });
  4. somePromise().then(null, function (err) {
  5. // handle error
  6. });

但下列并不等价

  1. somePromise().then(function () {
  2. return someOtherPromise();
  3. }).catch(function (err) {
  4. // handle error
  5. });
  6. somePromise().then(function () {
  7. return someOtherPromise();
  8. }, function (err) {
  9. // handle error
  10. });
  11. // 第二个的问题在于
  12. somePromise().then(function () {
  13. throw new Error('oh noes');
  14. }, function (err) {
  15. // I didn't catch your error! :(
  16. });

错误8.

  1. Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
  2. console.log(result);
  3. });

实际结果为?
问题在于:没有给then方法传递一个函数,这样相当于then(null),导致前一个promise的结果传递给第三个。
应该为:

  1. Promise.resolve('foo').then(function () {
  2. return Promise.resolve('bar');
  3. }).then(function (result) {
  4. console.log(result);
  5. });

解决之前的题目

  1. doSomething().then(function () {
  2. return doSomethingElse();
  3. }).then(finalHandler);
  4. // ======================================================================
  5. doSomething
  6. |-----------------|
  7. doSomethingElse(undefined)
  8. |------------------|
  9. finalHandler(resultOfDoSomethingElse)
  10. |------------------|
  1. doSomething().then(function () {
  2. doSomethingElse();
  3. }).then(finalHandler);
  4. // ======================================================================
  5. doSomething
  6. |-----------------|
  7. doSomethingElse(undefined)
  8. |------------------|
  9. finalHandler(undefined)
  10. |------------------|
  1. doSomething().then(doSomethingElse())
  2. .then(finalHandler);
  3. // ======================================================================
  4. doSomething
  5. |-----------------|
  6. doSomethingElse(undefined)
  7. |---------------------------------|
  8. finalHandler(resultOfDoSomething)
  9. |------------------|
  1. doSomething().then(doSomethingElse)
  2. .then(finalHandler);
  3. // ======================================================================
  4. doSomething
  5. |-----------------|
  6. doSomethingElse(resultOfDoSomething)
  7. |------------------|
  8. finalHandler(resultOfDoSomethingElse)
  9. |------------------|
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注