《understanding ESMAScript 6》之promise 和异步编程( Promises and Asynchronous Programming)

异步编程的背景 (Asynchronous Programming Background)

js 的引擎建立在单线程事件轮询( single threaded event loop)概念之上。

单线程意味着在一段时间内只能执行一段代码。所以我们不需要追踪这些运行的代码,而是在他们准备好执行时将他们放置到任务队列(job queue)。当代码由 js 引擎执行完毕后,引擎通过 event loop 找到并执行队列任务中的下一个任务。

事件模型 the event model

当一个用户点击了一个按钮或按下一个键盘上的某个按键时,一个事件如 onclick 会被触发。为了响应该交互,或许一个新的任务会被添加到任务队列中。这就是 JavaScript 异步编程中最基本的形式。关于处理事件的代码知道事件发生后才会执行,此时相应的上下文(context)会出现,例如:

let button = document.getElementById("my-btn");
button.onclick = function(event) {
  console.log("Clicked");
};

但是 如果按钮在注册之前被点击,那么什么事情都不会发生。由此,我们必须保证所有相应的处理程序在事件第一次放生之前注册完毕。

回调模式 the callback pattern

例如 nodejs:

readFile("example.txt", function(err, contents) {
  if (err) {
    throw err;
  }

  console.log(contents);
});
console.log("Hi!");

简单的回调当然可以这样写,但是如果多个回调串联在一起,例如:

readFile("example.txt", function(err, contents) {
  if (err) {
    throw err;
  }

  writeFile("example.txt", function(err) {
    if (err) {
      throw err;
    }

    console.log("File was written!");
  });
});

调用 readFile() 成功之后又出现了另一个异步调用 —— writeFile()

该模式使用起来感觉相当不错,不过当回调函数嵌套过多时,你很快就会发现自己陷入了回调地狱(callback hell)。像这样:

method1(function(err, result) {
  if (err) {
    throw err;
  }

  method2(function(err, result) {
    if (err) {
      throw err;
    }

    method3(function(err, result) {
      if (err) {
        throw err;
      }

      method4(function(err, result) {
        if (err) {
          throw err;
        }

        method5(result);
      });
    });
  });
});

缺点:

  1. 形成错综复杂的代码,难以阅读和调试。
  2. 容易出现错误

promise 基础 promise basic

promise 是异步操作结果的占位符,函数可以返回一个 promise,而不用订阅一个事件或向函数传递回调参数,like this:

// readFile 承诺在之后完成该操作
let promise = readFile("example.txt");

在这段代码中,readFile() 会稍后而非立即去读取文件。函数会返回一个 promise 对象来表示异步读取操作以便在之后你可以使用它。确切使用 promise 结果的时机完全取决于 promise 生命周期中的行为。

promise 的生命周期(the promise lifecycle)

一开始都会处于短暂的挂起(pending)状态,表示操作仍未完成,即挂起的 promise 被认为是未定的(unsettled),一旦操作完成,promise 就被认为是已定的(settled),并处于一下两种状态之一:

  1. fulfilled : promise 的异步操作已完成
  2. rejected: promise 的异步操作未完成。

以该种方式实现 then() 方法的对象被称为 thenable 。所有 promise 都是 thenable,但不是所有的的 thenable 都是 promise 。

promise 应用的简单方法以及方法执行的是什么

let promise = readFile("example.txt");

promise.then(
  function(contents) {
    // fulfillment
    console.log(contents);
  },
  function(err) {
    // rejection
    console.error(err.message);
  }
);

promise.then(function(contents) {
  // fulfillment
  console.log(contents);
});

promise.then(null, function(err) {
  // rejection
  console.error(err.message);
});
// 它的行为等效于只传递 rejection 处理(handler)
promise.catch(function(err) {
  // rejection
  console.error(err.message);
});

// 等效于:

promise.then(null, function(err) {
  // rejection
  console.error(err.message);
});

then()和 catch()都是来处理异步操作的。这种机制比事件和回调更优的原因是:

promise 操作结果成功或失败是一目了然的。

事件出现错误后不会被触发。

回调函数总是要查看 error 参数。

创建未定的 promise create unsettled promise

/**
 * resolve() 函数会在执行函数成功运行后发出信号表示该 promise 已经可用
 * reject() 函数代表改执行函数运行失败。
 * Promise(function(resolve,reject){
 *  // 异步任务 do something
 *  // resolve()
 *  // or reject()
 * })
 */
// Node.js 示例

let fs = require("fs");

function readFile(filename) {
  return new Promise(function(resolve, reject) {
    // 触发异步任务
    fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
      // 检查错误
      if (err) {
        reject(err);
        return;
      }

      // 读取操作成功
      resolve(contents);
    });
  });
}

let promise = readFile("example.txt");
/**
 * 同时监听 fulfillment 和 rejection
 * promise.then(处理成功,处理失败)
 */
promise.then(
  function(contents) {
    // fulfillment
    console.log(contents);
  },
  function(err) {
    // rejection
    console.error(err.message);
  }
);

需要注意的是,执行函数在 readFile()被调用后会立即执行。

当 resolve() 和 reject() 在执行函数内部被调用后,为了处理这个 promise,一个任务会被放置在任务队列中。这种行为叫做任务调度(job scheduling),在任务调度中,你向任务队列添加一个任务并声明:现在不要执行它。

可以看一个例子体会一下:

let promise = new Promise(function(resolve, reject) {
  console.log("Promise");
  resolve();
});

promise.then(function() {
  console.log("Resolved.");
});

console.log("Hi!");

该例子的输出为:

Promise
Hi!
Resolved

创建已定的 promise create settled promise

使用 Promise.resolve() (Using Promise.resolve())

Promise.resolve()方法接收单个参数并返回一个 fufilled 状态的 promise。这意味着没有发生任务调度。

let promise = Promise.resolve(42);

promise.then(function(value) {
  console.log(value); // 42
});

使用 Promise.reject() (Using Promise.reject())

Promise.reject() 方法来创建状态为 rejected 的 promise 。

let promise = Promise.reject(42);

promise.catch(function(value) {
  console.log(value); // 42
});

非 promise 的 thenable 对象(Non-Promise Thenables)

了解即可。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value); // 42
});
let thenable = {
  then: function(resolve, reject) {
    reject(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.catch(function(value) {
  console.log(value); // 42
});

promise 链 (chaining promises)

实际上,每一次调用 then() 和 catch() 都会返回另一个 promise。

它只会在之前的 promise 转化为 fulfilled 或 rejected 状态的那一刻后才会被处理 。

let p1 = new Promise(function(resolve, reject) {
  resolve(42);
});

p1
  .then(function(value) {
    console.log(value);
  })
  .then(function() {
    console.log("Finished");
  });

代码输出:

42;
Finished;

捕获错误 catching errors

promise 链允许你捕获上一个 promise 的 fulfillment 或 rejection 处理中的错误。例如:

let p1 = new Promise(function(resolve, reject) {
  resolve(42);
});

p1
  .then(function(value) {
    throw new Error("Boom!");
  })
  .catch(function(error) {
    console.log(error.message); // "Boom!"
  });

promise 链中的返回值 returning values in promise chains

机制:promise 链中的 promise 能够向下一个 promise 传递数据

let p1 = new Promise(function(resolve, reject) {
  resolve(42);
});

p1
  .then(function(value) {
    console.log(value); // "42"
    return value + 1;
  })
  .then(function(value) {
    console.log(value); // "43"
  });

promise 链中的 promise 返回 returning promises in promise chains

在 promise 链中返回一个 promise:

let p1 = new Promise(function(resolve, reject) {
  resolve(42);
});

let p2 = new Promise(function(resolve, reject) {
  resolve(43);
});

p1
  .then(function(value) {
    // 首个 fulfillment 处理
    console.log(value); // 42
    return p2;
  })
  .then(function(value) {
    // 第二个 fulfillment 处理
    console.log(value); // 43
  });

认识该模式重要的一点:第二个 fulfillment 处理并没有添加在 p2,而是第三个 promise 上。

响应多个 promise (responding to multiple promises)

ECMAScript 6 提供了两个方法负责此事:Promise.all() 和 Promise.race()

Promise.all() 方法(The Promise.all() Method)

let p1 = new Promise(function(resolve, reject) {
  resolve(42);
});

let p2 = new Promise(function(resolve, reject) {
  resolve(43);
});

let p3 = new Promise(function(resolve, reject) {
  resolve(44);
});

let p4 = Promise.all([p1, p2, p3]);

p4.then(function(value) {
  console.log(Array.isArray(value)); // true
  console.log(value[0]); // 42
  console.log(value[1]); // 43
  console.log(value[2]); // 44
});

记住:all()方法是 当所有的 promise (p1,p2,p3) fulfilled 之后 (p4)才会转变状态为 fulfilled。如果某个 promise 转变为 rejected 状态,那么 p4 会立即返回一个 rejected 状态的 promise 而不用等待其它 promise 完成执行。

Promise.race() 方法(The Promise.race() Method)

let p1 = Promise.resolve(42);

let p2 = new Promise(function(resolve, reject) {
  resolve(43);
});

let p3 = new Promise(function(resolve, reject) {
  resolve(44);
});

let p4 = Promise.race([p1, p2, p3]);

p4.then(function(value) {
  console.log(value); // 42
});

与 all 不同的是:传给 Promise.race() 的 promise 真如竞赛一般看哪一个先处于已定状态,并返回胜出的 fulfilled promise;p4 的 fulfillment 处理会被立即调用并传入值 42,其它的 promise 都被忽略。

promise 的继承 promise inheriting from promises

仅做了解:

假如,你想创建一个包含 success() 和 failure() 方法的 promise 但又不想丢掉内置版本中的 then() 和 catch(),你可以如下创建该 promise 类型:

class MyPromise extends Promise {
  // 使用默认的构造函数

  success(resolve, reject) {
    return this.then(resolve, reject);
  }

  failure(reject) {
    return this.catch(reject);
  }
}

let promise = new MyPromise(function(resolve, reject) {
  resolve(42);
});

promise
  .success(function(value) {
    console.log(value); // 42
  })
  .failure(function(value) {
    console.log(value);
  });

运行异步任务

let fs = require("fs");

function run(taskDef) {
  // 创建迭代器
  let task = taskDef();

  // 开始任务
  let result = task.next();

  // 使用函数递归进行迭代
  (function step() {
    // 如果还有工作要做
    if (!result.done) {
      // 使用 resolve() 来简化 promise 的处理
      let promise = Promise.resolve(result.value);
      promise
        .then(function(value) {
          result = task.next(value);
          step();
        })
        .catch(function(error) {
          result = task.throw(error);
          step();
        });
    }
  })();
}

// 定义任务运行器需要的函数

function readFile(filename) {
  return new Promise(function(resolve, reject) {
    fs.readFile(filename, function(err, contents) {
      if (err) {
        reject(err);
      } else {
        resolve(contents);
      }
    });
  });
}

// 运行一个任务

run(function*() {
  let contents = yield readFile("config.json");
  doSomethingWith(contents);
  console.log("Done");
});

更上一层楼 使用 await 和 async 函数来执行异步任务

它的基本理念是使用 async 标记的函数和 await 而不是生成器与 yield 来调用函数,例如:

(async function() {
  let contents = await readFile("config.json");
  doSomethingWith(contents);
  console.log("Done");
});

学习链接 : https://oshotokill.gitbooks.io/understandinges6-simplified-chinese/content/chapter_11.html

你可能感兴趣的:(ES6)