>

函数简化异步代码,中的神器

- 编辑:至尊游戏网站 -

函数简化异步代码,中的神器

用 Async 函数简化异步代码

2017/04/08 · JavaScript · 异步

原文出处: Joe Zimmerman , Nilson Jacques   译文出处:oschina   

Promise 在 JavaScript 上发布之初就在互联网上流行了起来 — 它们帮开发人员摆脱了回调地狱,解决了在很多地方困扰 JavaScript 开发者的异步问题。但 Promises 也远非完美。它们一直请求回调,在一些复杂的问题上仍会有些杂乱和一些难以置信的冗余。

随着 ES6 的到来(现在被称作 ES2015),除了引入 Promise 的规范,不需要请求那些数不尽的库之外,我们还有了生成器。生成器可在函数内部停止执行,这意味着可把它们封装在一个多用途的函数中,我们可在代码移动到下一行之前等待异步操作完成。突然你的异步代码可能就开始看起来同步了。

这只是第一步。异步函数因今年加入 ES2017,已进行标准化,本地支持也进一步优化。异步函数的理念是使用生成器进行异步编程,并给出他们自己的语义和语法。因此,你无须使用库来获取封装的实用函数,因为这些都会在后台处理。

运行文章中的 async/await 实例,你需要一个能兼容的浏览器。

姓名:岳沁

运行兼容

在客户端,Chrome、Firefox 和 Opera 能很好地支持异步函数。

图片 1

(点击图片进行页面跳转)

从 7.6 版本开始,Node.js 默认启用 async/await。

学号:17101223458

异步函数和生成器对比

这有个使用生成器进行异步编程的实例,用的是 Q 库:

var doAsyncOp = Q.async(function* () {   var val = yield asynchronousOperation();   console.log(val);   return val; });

1
2
3
4
5
var doAsyncOp = Q.async(function* () {
  var val = yield asynchronousOperation();
  console.log(val);
  return val;
});

Q.async 是个封装函数,处理场景后的事情。其中 * 表示作为一个生成器函数的功能,yield 表示停止函数,并用封装函数代替。Q.async 将会返回一个函数,你可对它赋值,就像赋值 doAsyncOp 一样,随后再调用。

ES7 中的新语法更简洁,操作示例如下:

async function doAsyncOp () {   var val = await asynchronousOperation();        console.log(val);   return val; };

1
2
3
4
5
async function doAsyncOp () {
  var val = await asynchronousOperation();     
  console.log(val);
  return val;
};

差异不大,我们删除了一个封装的函数和 * 符号,转而用 async 关键字代替。yield 关键字也被 await 取代。这两个例子事实上做的事是相同的:在 asynchronousOperation 完成之后,赋值给 val,然后进行输出并返回结果。

转载自:

将 Promises 转换成异步函数

如果我们使用 Vanilla Promises 的话前面的示例将会是什么样?

function doAsyncOp () {   return asynchronousOperation().then(function(val) {     console.log(val);     return val;   }); };

1
2
3
4
5
6
function doAsyncOp () {
  return asynchronousOperation().then(function(val) {
    console.log(val);
    return val;
  });
};

这里有相同的代码行数,但这是因为 then 和给它传递的回调函数增加了很多的额外代码。另一个让人厌烦的是两个 return 关键字。这一直有些事困扰着我,因为它很难弄清楚使用 promises 的函数确切的返回是什么。

就像你看到的,这个函数返回一个 promises,将会赋值给 val,猜一下生成器和异步函数示例做了什么!无论你在这个函数返回了什么,你其实是暗地里返回一个 promise 解析到那个值。如果你根本就没有返回任何值,你暗地里返回的 promise 解析为 undefined。

【嵌牛导读】:ES6 原生提供了 Promise 对象。

链式操作

Promise 之所以能受到众人追捧,其中一个方面是因为它能以链式调用的方式把多个异步操作连接起来,避免了嵌入形式的回调。不过 async 函数在这个方面甚至比 Promise 做得还好。

下面演示了如何使用 Promise 来进行链式操作(我们只是简单的多次运行 asynchronousOperation 来进行演示)。

function doAsyncOp() {   return asynchronousOperation()     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     }); }

1
2
3
4
5
6
7
8
9
10
11
12
function doAsyncOp() {
  return asynchronousOperation()
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    });
}

使用 async 函数,只需要像编写同步代码那样调用 asynchronousOperation:

async function doAsyncOp () {   var val = await asynchronousOperation();   val = await asynchronousOperation(val);   val = await asynchronousOperation(val);   return await asynchronousOperation(val); };

1
2
3
4
5
6
async function doAsyncOp () {
  var val = await asynchronousOperation();
  val = await asynchronousOperation(val);
  val = await asynchronousOperation(val);
  return await asynchronousOperation(val);
};

甚至最后的 return 语句中都不需要使用 await,因为用或不用,它都返回了包含了可处理终值的 Promise。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

并发操作

Promise 还有另一个伟大的特性,它们可以同时进行多个异步操作,等他们全部完成之后再继续进行其它事件。ES2015 规范中提供了 Promise.all(),就是用来干这个事情的。

这里有一个示例:

function doAsyncOp() {   return Promise.all([     asynchronousOperation(),     asynchronousOperation()   ]).then(function(vals) {     vals.forEach(console.log);     return vals;   }); }

1
2
3
4
5
6
7
8
9
function doAsyncOp() {
  return Promise.all([
    asynchronousOperation(),
    asynchronousOperation()
  ]).then(function(vals) {
    vals.forEach(console.log);
    return vals;
  });
}

Promise.all() 也可以当作 async 函数使用:

async function doAsyncOp() {   var vals = await Promise.all([     asynchronousOperation(),     asynchronousOperation()   ]);   vals.forEach(console.log.bind(console));   return vals; }

1
2
3
4
5
6
7
8
async function doAsyncOp() {
  var vals = await Promise.all([
    asynchronousOperation(),
    asynchronousOperation()
  ]);
  vals.forEach(console.log.bind(console));
  return vals;
}

这里就算使用了 Promise.all,代码仍然很清楚。

【嵌牛鼻子】:Promise

处理拒绝

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

function doAsyncOp() {   return asynchronousOperation()     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     })     .catch(function(err) {       console.error(err);     }); }

1
2
3
4
5
6
7
8
9
10
11
12
function doAsyncOp() {
  return asynchronousOperation()
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .catch(function(err) {
      console.error(err);
    });
}

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

async function doAsyncOp () {   try {     var val = await asynchronousOperation();     val = await asynchronousOperation(val);     return await asynchronousOperation(val);   } catch (err) {     console.err(err);   } };

1
2
3
4
5
6
7
8
9
async function doAsyncOp () {
  try {
    var val = await asynchronousOperation();
    val = await asynchronousOperation(val);
    return await asynchronousOperation(val);
  } catch (err) {
    console.err(err);
  }
};

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

【嵌牛提问】:如何提高Promise效率?

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       reject("something is bad");     }     resolve("nothing is bad");   }); } /*-- or --*/ function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       reject(new Error("something is bad"));     }     resolve("nothing is bad");   }); } /*-- or --*/ function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       throw new Error("something is bad");     }     resolve("nothing is bad");   }); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      reject("something is bad");
    }
    resolve("nothing is bad");
  });
}
 
/*-- or --*/
 
function doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      reject(new Error("something is bad"));
    }
    resolve("nothing is bad");
  });
}
 
/*-- or --*/
 
function doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      throw new Error("something is bad");
    }
    resolve("nothing is bad");
  });
}

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

function doAsyncOp() {   // the next line will kill execution   throw new Error("something is bad");   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       throw new Error("something is bad");     }     resolve("nothing is bad");   }); } // assume `doAsyncOp` does not have the killing error function x() {   var val = doAsyncOp().then(function() {     // this one will work just fine     throw new Error("I just think an error should be here");   });   // this one will kill execution   throw new Error("The more errors, the merrier");   return val; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function doAsyncOp() {
  // the next line will kill execution
  throw new Error("something is bad");
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      throw new Error("something is bad");
    }
    resolve("nothing is bad");
  });
}
 
// assume `doAsyncOp` does not have the killing error
function x() {
  var val = doAsyncOp().then(function() {
    // this one will work just fine
    throw new Error("I just think an error should be here");
  });
  // this one will kill execution
  throw new Error("The more errors, the merrier");
  return val;
}

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 Promise 抓住:

async function doAsyncOp() {   // the next line is fine   throw new Error("something is bad");   if (somethingIsBad) {     // this one is good too     throw new Error("something is bad");   }   return "nothing is bad"; }  // assume `doAsyncOp` does not have the killing error async function x() {   var val = await doAsyncOp();   // this one will work just fine   throw new Error("I just think an error should be here");   return val; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function doAsyncOp() {
  // the next line is fine
  throw new Error("something is bad");
  if (somethingIsBad) {
    // this one is good too
    throw new Error("something is bad");
  }
  return "nothing is bad";
 
// assume `doAsyncOp` does not have the killing error
async function x() {
  var val = await doAsyncOp();
  // this one will work just fine
  throw new Error("I just think an error should be here");
  return val;
}

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

【嵌牛正文】:

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

async function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(function(fileName) {       var file = await getFileAsync(fileName);       return parse(file);     })   ); }

1
2
3
4
5
6
7
8
async function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(function(fileName) {
      var file = await getFileAsync(fileName);
      return parse(file);
    })
  );
}

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

async function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(async function(fileName) {       var file = await getFileAsync(fileName);       return parse(file);     })   ); }

1
2
3
4
5
6
7
8
async function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(async function(fileName) {
      var file = await getFileAsync(fileName);
      return parse(file);
    })
  );
}

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(function(fileName) {       return getFileAsync(fileName).then(function(file) {         return parse(file);       });     })   ); }

1
2
3
4
5
6
7
8
9
function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(function(fileName) {
      return getFileAsync(fileName).then(function(file) {
        return parse(file);
      });
    })
  );
}

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

var a = doAsyncOp(); // one of the working ones from earlier console.log(a); a.then(function() {   console.log("`a` finished"); }); console.log("hello"); /* -- will output -- */ Promise Object hello `a` finished

1
2
3
4
5
6
7
8
9
10
11
var a = doAsyncOp(); // one of the working ones from earlier
console.log(a);
a.then(function() {
  console.log("`a` finished");
});
console.log("hello");
 
/* -- will output -- */
Promise Object
hello
`a` finished

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

Promise in js

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

1 赞 1 收藏 评论

图片 2

回调函数真正的问题在于他剥夺了我们使用 return 和 throw 这些关键字的能力。而 Promise 很好地解决了这一切。

2015 年 6 月,ECMAScript 6 的正式版终于发布了。

ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现。ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言。

概念

ES6 原生提供了 Promise 对象。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

Promise 对象有以下两个特点。

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

var promise = new Promise(function(resolve, reject) {

if (/* 异步操作成功 */){

resolve(value);

} else {

reject(error);

}

});

promise.then(function(value) {

// success

}, function(value) {

// failure

});

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

基本的 api

Promise.resolve()

Promise.reject()

Promise.prototype.then()

Promise.prototype.catch()

Promise.all()    // 所有的完成

var p = Promise.all([p1,p2,p3]);

Promise.race()      // 竞速,完成一个即可

进阶

promises 的奇妙在于给予我们以前的 return 与 throw,每个 Promise 都会提供一个 then() 函数,和一个 catch(),实际上是 then(null, ...) 函数,

somePromise().then(functoin(){

// do something

});

我们可以做三件事,

  1. return 另一个 promise

  2. return 一个同步的值 (或者 undefined)

  3. throw 一个同步异常throw new Eror('');

  4. 封装同步与异步代码

```

new Promise(function (resolve, reject) {

resolve(someValue);

});

```

写成

```

Promise.resolve(someValue);

```

  1. 捕获同步异常

new Promise(function (resolve, reject) {

throw new Error('悲剧了,又出 bug 了');

}).catch(function(err){

console.log(err);

});

如果是同步代码,可以写成

Promise.reject(new Error("什么鬼"));

  1. 多个异常捕获,更加精准的捕获

somePromise.then(function() {

return a.b.c.d();

}).catch(TypeError, function(e) {

//If a is defined, will end up here because

//it is a type error to reference property of undefined

}).catch(ReferenceError, function(e) {

//Will end up here if a wasn't defined at all

}).catch(function(e) {

//Generic catch-the rest, error wasn't TypeError nor

//ReferenceError

});

  1. 获取两个 Promise 的返回值

  2. .then 方式顺序调用

  3. 设定更高层的作用域

  4. spread

  5. finally

任何情况下都会执行的,一般写在 catch 之后

  1. bind

somethingAsync().bind({})

.spread(function (aValue, bValue) {

this.aValue = aValue;

this.bValue = bValue;

return somethingElseAsync(aValue, bValue);

})

.then(function (cValue) {

return this.aValue + this.bValue + cValue;

});

或者 你也可以这样

var scope = {};

somethingAsync()

.spread(function (aValue, bValue) {

scope.aValue = aValue;

scope.bValue = bValue;

return somethingElseAsync(aValue, bValue);

})

.then(function (cValue) {

return scope.aValue + scope.bValue + cValue;

});

然而,这有非常多的区别,

你必须先声明,有浪费资源和内存泄露的风险

不能用于放在一个表达式的上下文中

效率更低

  1. all。非常用于于处理一个动态大小均匀的 Promise 列表

  2. join。非常适用于处理多个分离的 Promise

```

var join = Promise.join;

join(getPictures(), getComments(), getTweets(),

function(pictures, comments, tweets) {

console.log("in total: " + pictures.length + comments.length + tweets.length);

});

```

  1. props。处理一个 promise 的 map 集合。只有有一个失败,所有的执行都结束

```

Promise.props({

pictures: getPictures(),

comments: getComments(),

tweets: getTweets()

}).then(function(result) {

console.log(result.tweets, result.pictures, result.comments);

});

```

  1. any 、some、race

```

Promise.some([

ping("ns1.example.com"),

ping("ns2.example.com"),

ping("ns3.example.com"),

ping("ns4.example.com")

], 2).spread(function(first, second) {

console.log(first, second);

}).catch(AggregateError, function(err) {

err.forEach(function(e) {

console.error(e.stack);

});

});;

```

有可能,失败的 promise 比较多,导致,Promsie 永远不会 fulfilled

  1. .map(Function mapper [, Object options])

用于处理一个数组,或者 promise 数组,

Option: concurrency 并发现

map(..., {concurrency: 1});

以下为不限制并发数量,读书文件信息

var Promise = require("bluebird");

var join = Promise.join;

var fs = Promise.promisifyAll(require("fs"));

var concurrency = parseFloat(process.argv[2] || "Infinity");

var fileNames = ["file1.json", "file2.json"];

Promise.map(fileNames, function(fileName) {

return fs.readFileAsync(fileName)

.then(JSON.parse)

.catch(SyntaxError, function(e) {

e.fileName = fileName;

throw e;

})

}, {concurrency: concurrency}).then(function(parsedJSONs) {

console.log(parsedJSONs);

}).catch(SyntaxError, function(e) {

console.log("Invalid JSON in file " + e.fileName + ": " + e.message);

});

结果

$ sync && echo 3 > /proc/sys/vm/drop_caches

$ node test.js 1

reading files 35ms

$ sync && echo 3 > /proc/sys/vm/drop_caches

$ node test.js Infinity

reading files: 9ms

  1. .reduce(Function reducer [, dynamic initialValue]) -> Promise

Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {

return fs.readFileAsync(fileName, "utf8").then(function(contents) {

return total + parseInt(contents, 10);

});

}, 0).then(function(total) {

//Total is 30

});

  1. Time

.delay(int ms) -> Promise

.timeout(int ms [, String message]) -> Promise

Promise 的实现

q

bluebird

co

when

ASYNC

async 函数与 Promise、Generator 函数一样,是用来取代回调函数、解决异步操作的一种方法。它本质上是 Generator 函数的语法糖。async 函数并不属于 ES6,而是被列入了 ES7。

本文由设计建站发布,转载请注明来源:函数简化异步代码,中的神器