关于async-await:async异步工具

在es6中的async的语法中,能够参照java并发包实现一些有意思的异步工具,辅助在异步场景(个别指申请)下的开发。因为js是单线程,上面的实现都比java中实现简略 (抛除线程概念)。同时波及到js的执行机制,宏工作,微工作,async,promise相干内容,须要提前具备这些常识。 wait(期待)异步函数中,期待(相当于java线程中的阻塞)一段时间。 实现代码: async function wait(time = 0) { await new Promise(resolve => setTimeout(resolve, time)); // 防止转译成return await, 会在一些safari版本外面报错 return undefined;}模仿应用代码: (async () => { console.time(); await wait(1000); console.timeEnd(); // 输入: default: 1.002s})();Lock(锁)模仿java并发包中的Lock类实现锁操作。 保障同一个锁突围的异步代码执行过程中,同一时刻只有一段代码在执行。 该锁不合乎html5的的异步锁接口,而是提供一个java异步包中Lock接口的简略实现举荐应用场景:多个申请执行过程前,保障同一时刻只有一个token生效验证转换操作。 实现代码: export type Resolve<T = any> = (value: T | PromiseLike<T>) => void;export type Reject = (reason?: any) => void;export interface FlatPromise<T = any> { promise: Promise<T>; resolve: Resolve<T>; reject: Reject;};interface Key { key: number, resolve: Resolve,};/*** 创立一个扁平的promise* @returns Prmise*/function flatPromise<T = any>(): FlatPromise<T> { const result: any = {}; const promise = new Promise<T>((resolve, reject) => { result.resolve = resolve; result.reject = reject; }); result.promise = promise; return result as FlatPromise<T>;}class Lock { keys: Key[] = []; hasLock: boolean = false; idCount: number = 0; constructor() { this.keys = []; this.hasLock = false; this.idCount = 0; } _pushKey(resolve: Resolve) { this.idCount += 1; const key: Key = { key: this.idCount, resolve, }; this.keys.push(key); return key; } _removeKey(key: Key) { const index = this.keys.findIndex(item => item.key === key.key); if (index >= 0) { this.keys.splice(index, 1); } } /** * 获取锁. * 如果以后锁曾经锁定,那么就阻塞以后操作 */ async lock() { if (this.keys.length || this.hasLock) { const { promise, resolve } = flatPromise(); this._pushKey(resolve); await promise; return null; } this.hasLock = true; return null; } /** * 尝试获取锁. * 该函数如果没有指定一个无效的time,则立马返回一个后果:如果获取到锁则为true,反之为false. * 如果指定一个无效的time(time=0无效),则返回一个promise对象,改对象返回的后果为是否获取到锁 * @param time 最长等待时间 */ tryLock(time?: number) { if (time === undefined || Number.isNaN(Math.floor(time)) || time < 0) { if (this.hasLock) { return false; } this.lock(); return Promise.resolve(true); } if (!this.hasLock && !this.keys.length) { this.hasLock = true; return Promise.resolve(true); } const asyncFn = async () => { const { promise, resolve: res } = flatPromise(); const key = this._pushKey(res); setTimeout(() => { this._removeKey(key); key.resolve(false); }, time); const isTimeout = await promise; return isTimeout !== false; }; return asyncFn(); } async lockFn(asyncFn: () => Promise<void>) { await this.lock(); try { await asyncFn(); } finally { this.unlock(); } } /** * 开释锁 */ unlock() { if (this.keys.length === 0 && this.hasLock === true) { this.hasLock = false; return; } if (this.keys.length === 0) { return; } const index = Math.floor(Math.random() * this.keys.length); const key = this.keys[index]; this._removeKey(key); key.resolve(undefined); } toString() { return `${this.keys.length}-${this.hasLock}`; }}模仿应用代码: ...

June 18, 2022 · 4 min · jiezi

关于async-await:vue项目asyncawait封装axios请求

装置axiosnpm install axios --save 创立http.js文件import axios from "axios"/* 申请拦截器 */axios.interceptors.request.use( config => { /* 增加token */ let token = localstorage.getItem("token") || ""; if(token){ config.headers["token"] = token } return config; }, error => { return Promise.reject(error) })export const http = (url,params,method='GET',type='json') =>{ /* 设置工夫随机数,解决申请缓存问题 */ let _t = new Date().getTime(); if(method == 'GET' || method == 'get'){ if(params){ params._t = _t }else{ params={ _t } } } /* 设置申请头 */ if (method === "POST") { if (type == "json") { //参数是json类型 axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8"; } else { //参数是字符串类型 axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"; // params = Qs.stringify(params); } } /* 发送申请 */ return new Promise((resolve,reject) => { axios({ url, method, type, data:method != "GET" ? params : null, params:method == "GET" ? params : null }) .then(result => { /* 此处能够全局解决接口非凡状态码,比方token生效等 */ resolve(result.data) }) .catch(error => { reject(error) }) })}/* await to js */export const to = promise => { return promise.then(res => [null,res]).catch(error => [error])}创立api.js文件,设置接口申请解决import { http, to } from "./http";// ---- 全局接口 -----export const ceshiList = params => { return to(http("json/ceshi.json", params, "GET"));};api应用import { ceshiList } from "@/http/api.js";methods:{ async getList(){ let [err, res] = await ceshiList(); if (!err) { if (res.result.code == 200) { /* 接口申请正确处理 */ console.log(res); } } }}转化blob返回的数据const render = new FileReader();render.onload = function(){ let jsonData = JSON.parse(render.result)}render.readAsText(data)

December 20, 2021 · 1 min · jiezi

关于async-await:asyncawait-使用方式

先说一下async的用法,它作为一个关键字放到函数后面,用于示意函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞前面代码的执行。 写一个async 函数 async function timeout() { return 'hello world';}语法很简略,就是在函数后面加上async 关键字,来示意它是异步的,那怎么调用呢?async 函数也是函数,平时咱们怎么应用函数就怎么应用它,间接加括号调用就能够了,为了示意它没有阻塞它前面代码的执行,咱们在async 函数调用之后加一句console.log; async function timeout() { return 'hello world'}timeout();console.log('尽管在前面,然而我先执行');关上浏览器控制台,咱们看到了async 函数 timeout 调用了,然而没有任何输入,它不是应该返回 'hello world', 先不要焦急, 看一看timeout()执行返回了什么? 把下面的 timeout() 语句改为console.log(timeout()) async function timeout() { return 'hello world'}console.log(timeout());console.log('尽管在前面,然而我先执行');持续看控制台原来async 函数返回的是一个promise 对象,如果要获取到promise 返回值,咱们应该用then 办法, 持续批改代码 async function timeout() { return 'hello world'}timeout().then(result => { console.log(result);})console.log('尽管在前面,然而我先执行');看控制台 咱们获取到了"hello world', 同时timeout的执行也没有阻塞前面代码的执行,和咱们方才说的统一。 这时,你可能留神到控制台中的Promise 有一个resolved,这是async 函数外部的实现原理。如果async 函数中有返回一个值 ,当调用该函数时,外部会调用Promise.solve() 办法把它转化成一个promise 对象作为返回,但如果timeout 函数外部抛出谬误呢? 那么就会调用Promise.reject() 返回一个promise 对象, 这时批改一下timeout 函数 ...

August 30, 2021 · 3 min · jiezi

关于async-await:ES6新增语法七asyncawait

什么是asyncasync的意思是“异步”,顾名思义就是无关异步操作的关键字,async 是 ES7 才有的,与咱们之前说的Promise、Generator有很大的关联。 应用语法: async function name(param){ param //传递给函数的参数名称 statements //函数体 } name().then(function(res){ res//异步操作返回的后果 }) async 函数返回一个Promise对象,能够应用then办法增加回调函数。具体实例如下: async function show(){ return {a:12,b:15}}console.log(show())//Promise {<fulfilled>: {…}}show().then(res=>{ console.log("res",res)})什么是awaitawait关键字存在async函数表达式中,用于期待Promise对象,暂停执行,等到异步操作实现后,复原async函数的执行并返回解析值。如果把await放在asnyc函数体外,会报语法错误。 应用语法: asnyc function name(){ returnValue = await expression; } expression 是一个Promise对象或一个须要期待的值,针对所跟不同表达式,有两种解决形式: 对于Promise对象,await会阻塞主函数执行,期待Promise对象执行resolve之后,resolve返回值作为await表达式运算后果,而后持续向下执行。 对于非Promise对象,能够是字符串、布尔值、数值以及一般函数等。await间接返回对应的值,而不是期待其执行后果。 await期待Promise对象实例如下: async function test1(){ console.log("执行") return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log("提早3秒之后返回胜利") resolve({a:'1'}) },3000) }) }async function test2(){ let x = await test1() console.log("x",x)//{a: "1"} return x}test2().then(function(res){ console.log("res",res)//{a: "1"}})await 跟 一般函数 实例如下: ...

July 22, 2021 · 1 min · jiezi

关于async-await:asyncawait是如何捕获异常的

function 猜大小(猜想) { return new Promise((resolve, reject) => { // 背下来 console.log("开始摇色子"); setTimeout(() => { let n = 6; //parseInt(Math.random() * 6 + 1, 10); // 1~6 if (n > 3) { if (猜想 === "大") { resolve(n); } else { reject(n); } } else { if (猜想 === "小") { resolve(n); } else { reject(n); } } }, 1000); });}async function test() { try { let n /*[6,6]*/ = await Promise.all([猜大小("大"), 猜大小("大")]); console.log("好嗨哦" + n); } catch (error) { console.log("输光了" + error); }}var result = test();console.log(result);

January 16, 2021 · 1 min · jiezi

关于async-await:async和await原理

generatorasync和await是generator的语法糖,想要搞清楚async和await必须晓得generator;那么咱们先来看看generator函数; function * getAge() { yield 1; yield 2;}let gen = getAge();console.log(gen.next());//{ value: 1, done: false }console.log(gen.next());//{ value: 2, done: false }console.log(gen.next());//{ value: undefined, done: true }generator和一般函数的区别: 1.generator申明函数之前要加* 2.函数中有yield,能够管制函数的运行 generator传参问题function * getAge() { let a = yield 1; console.log(a);//444 yield 2;}let gen = getAge();console.log(gen.next());console.log(gen.next(444));generator传参能够将参数传在next函数中,给a赋值,然而第一个next()函数永远是undefined;如下 function * getAge(xxx) { let a = yield 1; console.log(a);//undefined yield 2;}let gen = getAge();console.log(gen.next(444));console.log(gen.next());generator函数运行过程 generator实现读取异步函数当初有一个name和age的txt文件,咱们通过name.txt读取age.txt里的内容;我这里的name中写的是age.txt,age.txt中写的是18; let {promisify} = require('util');let fs = require('fs');let read = promisify(fs.readFile);function * getAge() { let name = yield read(`${__dirname}name.txt`, 'utf-8'); let age = yield read(`${__dirname}${name}`, 'utf-8');}let it = getAge();let {value} = it.next();//返回的是读取name.txt的promise函数value.then(data => { let {value} = it.next(data)//将读取name文件的内容作为参数赋值给getAge函数中的name,同时这里的value是读取age.txt的promise函数 value.then(data => { console.log(data)//18 })});async和await下面层层嵌套看着很乱尤其是嵌套多的状况下,那么咱们应用async和await实现一下这里只是将generator函数中的*换成了async,将yield换成了await ...

November 18, 2020 · 2 min · jiezi

asyncawait-和-Promise-的用例关系

假设我们有三个请求,req1,req2, req3,三个请求后者依赖前者的请求结果。我们先使用Promise封装一个异步请求的方法。 Promise 异步请求使用Promise可以非常容易的封装一个异步处理的业务,通过reslove/reject两个callback来返回执行结果。 我们使用 Promise 封装一个 http get方法。 // 返回一个 Promise 对象(PromiseStatus: pending)function asyncHttpGet(url) { return new Promise((resolve, reject) => { const request = new Request(url, {method: 'GET'}) // 使用 fetch 请求 fetch(request) .then(response => { if (200 == response.status) { return response.text() } else { // goto catch throw new Error("request failed: " + response.status) } }).then(html => { // console.log(html) resolve(url + " get success") }).catch(err => { reject(err) }); })}fetch返回的其实就是一个Promise对象,上例是想演示下resolve/reject的使用上下文,如果你早已get,下面给出直接使用fetch的方式: ...

July 16, 2019 · 4 min · jiezi

翻译Taming-the-asynchronous-beast-with-ES7

原文:https://pouchdb.com/2015/03/0... PouchDB最棘手的方面之一是它的API是异步的。在Stack Overflow、Github和IRC上,我看到了不少困惑的问题,而且这些问题通常是由对callbacks和promises的误解造成的。我们真的无能为力。PouchDB是对IndexedDB, WebSQL, LevelDB (in Node), and CouchDB (via Ajax)的抽象。所有这些API都是异步的;因此PouchDB必须是异步的。然而,当我想到优雅的数据库API时,我仍然对LocalStorage的简单性感到震惊: if (!localStorage.foo) { localStorage.foo = 'bar'; }; console.log(localStorage.foo);要使用LocalStorage,您只需将它当作一个神奇的javascript对象来保存数据。它使用的同步工具集与使用JavaScript本身时已经习惯的工具集相同。对于LocalStorage的所有错误(https://www.html5rocks.com/en/tutorials/offline/quota-research/),这个API的人机工程学在很大程度上解释了它的持续流行。人们一直在使用LocalStorage,因为它很简单,而且工作正常。Promises aren't a panacea对于PouchDB,我们可以尝试通过promises来减轻异步API的复杂性,这当然有助于我们摆脱pyramid of doom。然而,promisey代码仍然很难阅读,因为promisey基本上是语言原语(如try、catch和return)的bolt-on替换: var db = new PouchDB('mydb'); db.post({}).then(function (result) { // post a new doc return db.get(result.id); // fetch the doc }).then(function (doc) { console.log(doc); // log the doc }).catch(function (err) { console.log(err); // log any errors });作为JavaScript开发人员,我们现在有两个并行系统——sync and async——我们必须直截了当地记住这两个系统。当我们的控制流变得更复杂时,情况会变得更糟,我们需要使用promise.all()和promise.resolve()等API。或者我们只是选择了众多帮助程序库中的一个,并祈祷我们能够理解文档。直到最近,这是我们所能期望的最好的。但所有这些都随ES7而改变。Enter ES7如果我告诉你,有了ES7,你可以把上面的代码改写成这样:let db = new PouchDB('mydb');try { let result = await db.post({}); let doc = await db.get(result.id); console.log(doc);} catch (err) { console.log(err);}如果我告诉你,多亏了Babel.js和Regenerator这样的工具,你现在可以将其发展到ES5并在浏览器中运行它了?女士们先生们,请大家鼓掌,直到博文结束。首先,让我们看看ES7是如何完成这一惊人的壮举的。Async functionsES7为我们提供了一种新的函数,async函数。在async函数内部,我们有一个新的关键字wait,用于“wait for”一个promise: async function myFunction() { let result = await somethingThatReturnsAPromise(); console.log(result); // cool, we have a result }如果promise resolves,我们可以在下一行立即与之交互。如果它拒绝了,那么就会抛出一个错误。所以,try/catch实际上再次有效! async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); // oh noes, we got an error } }这允许我们编写表面上看起来是同步的,但实际上是异步的代码。API返回一个promise 而不是阻塞事件循环这一事实只是一个实现细节。还记得你什么时候可以只使用'return'和'try/catch'吗?最好的一点是,我们今天可以把它和任何一个可以返回promises 的库一起使用。PouchDB就是这样一个库,所以让我们用它来测试我们的理论。Managing errors and return values首先,考虑一下pouchdb中的一个常见习惯用法:如果文档存在,我们希望按_id获取一个文档,如果不存在,则返回一个新文档。有了promises,你就必须写下这样的东西: db.get('docid').catch(function (err) { if (err.name === 'not_found') { return {}; // new doc } throw err; // some error other than 404 }).then(function (doc) { console.log(doc); })对于异步函数,这将变成: let doc; try { doc = await db.get('docid'); } catch (err) { if (err.name === 'not_found') { doc = {}; } else { throw err; // some error other than 404 } } console.log(doc); 可读性更高!如果db.get()直接返回一个文档而不是一个promise,那么这几乎是我们编写的代码。唯一的区别是,当我们调用任何promise-returning函数时,必须添加wait关键字。Potential gotchas我在玩这个的时候遇到了一些微妙的问题,所以很高兴能意识到它们。首先,当您等待某件事情时,您需要在一个async函数中。因此,如果您的代码严重依赖PouchDB,您可能会发现您编写了许多async函数,但很少有常规函数。另一个更阴险的问题是,您必须小心地将代码包装在try/catch中,否则promise可能会被拒绝,在这种情况下,错误会被默默地吞没。(!)我的建议是确保您的async函数完全被try/catch包围,至少在顶层:async function createNewDoc() { let response = await db.post({}); // post a new doc return await db.get(response.id); // find by id}async function printDoc() { try { let doc = await createNewDoc(); console.log(doc); } catch (err) { console.log(err); }}Loops当涉及到迭代时,Async 函数会变得非常令人印象深刻。例如,假设我们希望将一些文档按顺序插入到数据库中。也就是说,我们希望这些promises一个接一个地执行,而不是同时执行。使用标准的ES6承诺,我们必须滚动自己的promise链: var promise = Promise.resolve(); var docs = [{}, {}, {}]; docs.forEach(function (doc) { promise = promise.then(function () { return db.post(doc); }); }); promise.then(function () { // now all our docs have been saved });这是可行的,但确实很难看。这也很容易出错,因为如果您不小心做了: docs.forEach(function (doc) { promise = promise.then(db.post(doc)); });然后promises实际上会同时执行,这可能会导致意想不到的结果。但是,使用ES7,我们可以使用常规for循环: let docs = [{}, {}, {}]; for (let i = 0; i < docs.length; i++) { let doc = docs[i]; await db.post(doc); }这个(非常简洁的)代码与promise链的作用是一样的!我们可以通过以下方式使其更短: let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); }注意,这里不能使用foreach()循环,如果你天真地写: let docs = [{}, {}, {}]; // WARNING: this won't work docs.forEach(function (doc) { await db.post(doc); });然后Babel.js将失败,并出现一些不透明的错误: Error : /../script.js: Unexpected token (38:23) > 38 | await db.post(doc); | ^这是因为在正常函数中不能使用wait。您必须使用async函数。但是,如果您尝试使用async函数,那么您将得到一个更微妙的错误: let docs = [{}, {}, {}]; // WARNING: this won't work docs.forEach(async function (doc, i) { await db.post(doc); console.log(i); }); console.log('main loop done');这将编译,但问题是这将打印出来: main loop done 0 1 2发生的是,主函数提前退出,因为await实际上在子函数中。此外,这将同时执行每一个promise,这不是我们的预期。教训是:在async函数中有任何函数时要小心。wait只会暂停它的父函数,所以检查它是否在做你认为它在做的事情。Concurrent loops但是,如果我们确实希望同时执行多个promises,那么使用ES7很容易实现这一点。回想一下,有了ES6 promises,我们就有了promise.all()。让我们使用它从promises数组中返回一个值数组: var docs = [{}, {}, {}]; return Promise.all(docs.map(function (doc) { return db.post(doc); })).then(function (results) { console.log(results); });在ES7中,我们可以这样做,这是一种更简单的方法: let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results);最重要的部分是1)创建promises数组,该数组立即调用所有的promises;2)我们在主函数中等待这些promises。如果我们尝试使用Array.prototype.map,那么它将无法工作: let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); // WARNING: this doesn't work let results = promises.map(async function(promise) { return await promise; }); // This will just be a list of promises :( console.log(results);不起作用的原因是我们在等待子函数的内部,而不是主函数。所以在我们真正等待完成之前,主函数就退出了。如果您不介意使用promise.all,也可以使用它来整理代码: let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results);如果我们使用数组压缩,这看起来可能会更好。然而,规范还不是最终的,所以目前Regenerator不支持它。CaveatsES7仍然非常前沿。Node.js 或 io.js都不支持Async函数,您必须设置一些实验标志,甚至让babel考虑它。正式来说,async/await规范(https://github.com/tc39/ecmascript-asyncawait#status-of-this-proposal)仍处于“建议”阶段。另外,为了在ES5浏览器中工作,您还需要在您的开发代码中包含Regenerator运行时和ES6 shims。对我来说,这加起来大约60kb,缩小和gzip。对于许多开发人员来说,这实在是太多了。然而,所有这些新工具都非常有趣,它们描绘了异步库在阳光明媚的ES7未来的美好图景。所以,如果你想自己玩,我已经建立了一个小的演示库(https://github.com/nolanlawson/async-functions-in-pouchdb)。要开始,只需检查代码,运行npm安装和npm运行build,就可以了。关于ES7的更多信息,请看JafarHusain的演讲。Conclusion异步函数是ES7中的一个新概念。它们将我们丢失的returns和try/catches返回给我们,并奖励我们已经从使用新的IDIOM编写同步代码中获得的知识,这些IDIOM看起来很像旧的IDIOM,但性能更高。最重要的是,async函数使得像PouchDB这样的API更容易使用。因此,希望这将减少用户错误和混淆,以及更优雅和可读的代码。谁知道呢,也许人们最终会放弃LocalStorage,选择更现代的客户端数据库。

July 6, 2019 · 3 min · jiezi

JavaScript异步编程

前言从我们一开始学习JavaScript的时候就听到过一段话:JS是单线程的,天生异步,适合IO密集型,不适合CPU密集型。但是,多数JavaScript开发者从来没有认真思考过自己程序中的异步到底是怎么出现的,以及为什么会出现,也没有探索过处理异步的其他方法。到目前为止,还有很多人坚持认为回调函数就完全够用了。 但是,随着JavaScript面临的需求越来越多,它可以运行在浏览器、服务器、甚至是嵌入式设备上,为了满足这些需求,JavaScript的规模和复杂性也在持续增长,使用回调函数来管理异步也越来越让人痛苦,这一切,都需要更强大、更合理的异步方法,通过这篇文章,我想对目前已有JavaScript异步的处理方式做一个总结,同时试着去解释为什么会出现这些技术,让大家对JavaScript异步编程有一个更宏观的理解,让知识变得更体系化一些。 正文Step1 - 回调函数回调函数大家肯定都不陌生,从我们写一段最简单的定时器开始: setTimeout(function () { console.log('Time out');}, 1000);定时器里面的匿名函数就是一个回调函数,因为在JS中函数是一等公民,所以它可以像其他变量一样作为参数进行传递。这样看来,通过回调函数来处理异步挺好的,写着也顺手,为什么要用别的方法呢? 我们来看这样一个需求: 上面是微信小程序的登录时序图,我们的需求和它类似但又有些差别,想要获取一段业务数据,整个过程分为3步: 调用秘钥接口,获取key携带key调用登录接口,获取token和userId携带token和userId调用业务接口,获取数据可能上述步骤和实际业务中的有些出入,但是却可以用来说明问题,请大家谅解。 我们写一段代码来实现上述需求: let key, token, userId;$.ajax({ type: 'get', url: 'http://localhost:3000/apiKey', success: function (data) { key = data; $.ajax({ type: 'get', url: 'http://localhost:3000/getToken', data: { key: key }, success: function (data) { token = data.token; userId = data.userId; $.ajax({ type: 'get', url: 'http://localhost:3000/getData', data: { token: token, userId: userId }, success: function (data) { console.log('业务数据:', data); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); }});可以看到,整段代码充满了回调嵌套,代码不仅在纵向扩展,横向也在扩展。我相信,对于任何人来说,调试起来都会很困难,我们不得不从一个函数跳到下一个,再跳到下一个,在整个代码中跳来跳去以查看流程,而最终的结果藏在整段代码的中间位置。真实的JavaScript程序代码可能要混乱的多,使得这种追踪难度会成倍增加。这就是我们常说的回调地狱(Callback Hell)。 ...

July 2, 2019 · 4 min · jiezi

fetch使用ajax替代方案

fetch简介Fetch 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。 这种功能以前是使用 XMLHttpRequest实现的,Fetch提供了一个更好的替代方法 Fetch API 是基于 Promise 设计,使用了Promises 来处理结果/回调。旧浏览器不支持 Promise,需要使用 polyfill es6-promise 。 简单实现 fetch("http://192.168.43.169:8099/someInfo",{ method: 'post', }) .then(res=>{ console.log(response) // 包含响应结果的promise,只是一个 HTTP 响应,而不是真的JSON return response.json(); }) .then(res=>{ console.log(res) //js格式的json对象 })async await实现更方便 const fetchRequest = async () => { let response = await fetch("http://192.168.43.169:8099/teacher/resume", { method: 'post', }) let data = await response.json() console.log(data); //js格式的json对象 } fetchRequest()Response 对象属性: status:整数(默认值为200) 为response的状态码statusText: 字符串(默认值为"OK"),该值与HTTP状态码消息对应.ok:如上所示, 该属性是来检查response的状态是否在200-299(包括200,299)这个范围内.该属性返回一个Boolean值.方法:处理包含返回结果的promise对象的数据 例如 response.json()处理包含json结果的promise对象 ...

June 28, 2019 · 2 min · jiezi

初级理解async实现之我见

在我们写项目代码的过程中,要经常请求接口数据,在某些异步请求数据之后,将得到的值进行处理。通俗的一句话就是,我要把这个值放到另一个函数中,按行数顺序处理,即同步的概念! 例子:第一步,涉及异步函数假设我有一个函数abc, function abc(){ //异步方法,请求数据得到result return result}第二步,没加入之前的处理我在另一个函数中拿到上面result值进行下一步处理: function xxx(){ let data; let val; //调用上面函数 let val = abc() data = val;}xxx()这样写因为abc函数里面涉及到了异步,所以data的值可能不是result的值,可是我们要取到result的值进行处理,怎么办? 第三步,加入async加入async await async function xxx(){ let data; let val; //调用上面函数 let val = await abc() data = val;}ok,完美解决!这就是通俗的同步。感谢大家的支持!

June 27, 2019 · 1 min · jiezi

译带你理解-Asyncawait

「async/await」是 promises 的另一种更便捷更流行的写法,同时它也更易于理解和使用。 Async functions让我们以 async 这个关键字开始。它可以被放置在任何函数前面,像下面这样: async function f() { return 1;}在函数前面的「async」这个单词表达了一个简单的事情:即这个函数总是返回一个 promise。即使这个函数在语法上返回了一个非 promise 的值,加了「async」这个关键字就会指示 JavaScript 引擎自动将返回值包装成一个解析后的 promise。 例如,以下的代码就返回了一个以 1 为结果的解析后的 promise, 让我们试一下: async function f() { return 1;}f().then(alert); // 1... 我们也可以显式返回一个 promise,结果是一样的: async function f() { return Promise.resolve(1);}f().then(alert); // 1所以说,async 确保了函数的返回值是一个 promise,也会包装非 promise 的值。很简单是吧?但是还没完。还有一个关键字叫 await,它只在 async 函数中有效,也非常酷。 Await语法如下: // 只在 async 函数中有效let value = await promise;关键字 await 让 JavaScript 引擎等待直到 promise 完成并返回结果。 这里的例子就是一个 1 秒后解析的 promise: ...

June 16, 2019 · 3 min · jiezi

Promiseasyncawait深入了解

1、Promise 的含义Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 Promise对象有以下两个特点。 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、resolved(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。 Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

May 30, 2019 · 1 min · jiezi

今日头条网红题-????-async-functions-和-promises哪个更快

题目如下 async function async1() { console.log('async1 start') await async2() console.log('async1 end')}async function async2() { console.log('async2')}console.log('script start')setTimeout(function() { console.log('setTimeout') }, 0) async1()new Promise(function(resolve) { console.log('promise1') resolve()}).then(function() { console.log('promise2')})console.log('script end')而v8和node10产出的结果有所不同。 v8运行结果???? node10运行结果???? 先说下async/await原理???? async 声明的函数,其返回值必定是 promise 对象,如果没有显式返回 promise 对象,也会用 Promise.resolve() 对结果进行包装,保证返回值为 promise 类型await 会先执行其右侧表达逻辑(从右向左执行),并让出主线程,跳出 async 函数,而去继续执行 async 函数外的同步代码如果 await 右侧表达逻辑是个 promise,让出主线程,继续执行 async 函数外的同步代码,等待同步任务结束后,且该promise 被 resolve 时,继续执行 await 后面的逻辑如果 await 右侧表达逻辑不是 promise 类型,那么 async 函数之外的同步代码执行完毕之后,会回到 async函数内部,继续执行 await 之后的逻辑-- 摘自 LucasHC ...

May 29, 2019 · 1 min · jiezi

ES6Async与异步编程11

单线程是Javascript语言最本质的特性之一,Javascript引擎在运行js代码的时候,同一个时间只能执行单个任务。 这种模式的好处是实现起来比较简单,执行环境相对单纯。 坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。 所以异步编程对JavaScript语言太重要。 有些小伙伴可能还不太理解"异步"。 所谓的"异步",就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。 例如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。 相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。 讲的通俗点: 朱自清的《背影》中,父亲对朱自清说 :“我买几个橘子去。你就在此地,不要走动。” 朱自清没有走动,等着买完橘子的父亲一起吃橘子,就叫同步。 如果朱自清没有等父亲,独自走了,那就不能和父亲一起吃橘子,就叫异步。 1、异步编程 我们就以用户注册这个特别常见的场景为例,讲讲异步编程。 第一步,验证用户是否注册 第二步,没有注册,发送验证码 第三步,填写验证码、密码,检验验证码是否正确 这个过程是有一定的顺序的,你必须保证上一步完成,才能顺利进行下一步。 1.1 回调函数 function testRegister(){} // 验证用户是否注册function sendMessage(){} // 给手机发送验证码xfunction testMessage(){} // 检验验证码是否正确function doRegister(){ //开始注册 testRegister(data){ if(data===false){ //已注册 }else{ //未注册 sendMessage(data){ if(data===true){ //发送验证码成功 testMessage(data){ if(data===true){ //验证码正确 }else{ //验证码不正确 } } } } } }}代码中就已经有许多问题,比如杂乱的 if 判断语句 、层层嵌套的函数,造成代码的可读性差,难于维护。 另外,如果在层层回调函数中出现异常,调试起来是非常让人奔溃的 —— 由于 try-catch 无法捕获异步的异常,我们只能不断不断的写 debugger 去追踪,简直步步惊心。 这种层层嵌套被称为回调地狱。 1.2 Promise方式 Promise就是为了解决回调地狱问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用Promise,连续读取多个文件,写法如下。 ...

May 15, 2019 · 2 min · jiezi

「 JS 」快速上手异步方案

问题:解决异步回调的深层嵌套的问题.(回调地狱)1. Promisepromise对象用于表示一个异步操作的最终状态,promise在回调代码和将要执行这个任务的异步代码之间提供了一种可靠的中间机制来管理回调。//构造函数,回调函数是同步的回调new Promise(function(resolve,reject){ ….//异步操作})Promise的实例对象有三个状态 pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败resolve和reject分别是两个函数,当在回调中调用时,会改变promise实例的状态,resolve改变状态为成功,reject为失败.thenPromise.prototype.then()当promise对象的状态发生改变时,绑定在其身上的then方法就会被调用。then方法包含两个参数:onfulfilled函数 和 onrejected函数,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争,then() 方法返回一个 Promise对象.返回值then方法返回一个新的Promise,而它的行为与then中的回调函数的返回值有关:如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。catchcatch() 方法返回一个Promise,并且处理拒绝的情况。Promise.prototype.catch()事实上,catch方法相当于then方法的第二个参数方法,触发拒绝状态.注意,如果调用 then的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。所以在链式上,最终会执行到catch上.//链式示例new Promise(function (resolve, reject) {setTimeout(function () {console.log(“1”);resolve();}, 1000);}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“2”);// resolve();reject();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“3”);resolve();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“4”);resolve();}, 1000);});}).catch(function(){console.log(“catch”);})2. genaratorsymbol新的一种基础数据类型symbol,表示独一无二的值.它通常作为对象的属性键,内置对象普遍存在该值.// 一般用法,它并不是构造器,不能通过new,会报错.let sym = Symbol();// 在对象中表现形式,要用[]包裹,不然会被认为是string.var obj = { [Symbol()]:“value”;}该属性是匿名,所以不可枚举,只能通过.getOwnPropertySymbols()返回的数组.// 想要获得两个相同的Symbol,得通过.for()Symbol(“asd”) === Symbol(“asd”) // falseSymbol.for(“asd”) === Symbol.for(“asd”) // trueIterator迭代器,存在于特定几种可枚举的数据类型中.// 一般用以下这种形式的键保存了迭代器函数.// arr[Symbol.iterator]aarrSymbol.iterator.next( ) //遍历下一个,返回value和done,value表示值,done表示是否可以继续遍历下一个.//for…of循环遍历就是基于此,必须该数据类型有迭代器.回到generator//表现形式function* test(){ yield 1; //任务1 yield 2; //任务2 yield 3; //任务3 yield 4 ; //任务4}// 调用该方法会返回一个含有迭代对象的对象.var obj = text();obj.next(); //调用该方法时,每次到一个yield处停止.3. async/await作用:简化promise的使用编码, 不通过then()/catch()来指定回调函数以同步编码方式实现异步流程async function test (){ // 等待状态改变,自动执行到下一个wait处 var flag = await new Promise((resolve,reject)=>{ setTimeout(function(){ // 状态改变 resolve(data); //这里面的值传递给flag },1000) }) //通过flag传递数据 flag = await new Promise((resolve,reject)=>{ setTimeout(function(flag){ // 状态改变 resolve(flag); },1000,flag) })}test().catch(function(err){ //处理异常}); ...

April 18, 2019 · 1 min · jiezi

JavaScript异步流程控制

JavaScript特性JavaScript属于单线程语言,即在同一时间,只能执行一个任务。在执行任务时,所有任务需要排队,前一个任务结束,才会执行后一个任务。当我们向后台发送一个请求时,主线程读取 “向后台发送请求” 这个事件并执行之后,到获取后台返回的数据这一过程会有段时间间隔,这时CPU处于空闲阶段,直到获取数据后再继续执行后面的任务,这就降低了用户体验度,使得页面加载变慢。于是,所有任务可以分成两种:同步任务和异步任务。同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制,这个过程会不断重复。“任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。JavaScript异步实现的5种方式1. callback(回调函数)回调函数,也被称为高阶函数,是一个被作为参数传递给另一个函数并在该函数中被调用的函数。看一个在JQuery中简单普遍的例子:// 注意: click方法是一个函数而不是变量$("#button”).click(function() { alert(“Button Clicked”);}); 可以看到,上述例子将一个函数作为参数传递给了click方法,click方法会调用该函数,这是JavaScript中回调函数的典型用法,它在jQuery中广泛被使用。它不会立即执行,因为我们没有在后面加( ),而是在点击事件发生时才会执行。比如,我们要下载一个gif,但是不希望在下载的时候阻断其他程序,可以实现如下:downloadPhoto(‘http://coolcats.com/cat.gif', handlePhoto)function handlePhoto (error, photo) { if (error) { console.error(‘Download error!’, error); } else { console.log(‘Download finished’, photo); }}console.log(‘Download started’)首先声明handlePhoto函数,然后调用downloadPhoto函数并传递handlePhoto作为其回调函数,最后打印出“Download started”。请注意,handlePhoto尚未被调用,它只是被创建并作为回调传入downloadPhoto。但直到downloadPhoto完成其任务后才能运行,这可能需要很长时间,具体取决于Internet连接的速度,所以运行代码后,会先打印出Download started。这个例子是为了说明两个重要的概念:handlePhoto回调只是稍后存储一些事情的一种方式;事情发生的顺序不是从顶部到底部读取,而是基于事情完成时跳转;1. callback hell(回调地狱)var fs = require(‘fs’);/** * 如果三个异步api操作的话 无法保证他们的执行顺序 * 我们在每个操作后用回调函数就可以保证执行顺序 */ fs.readFile(’./data1.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); fs.readFile(’./data2.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data) fs.readFile(’./data3.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); } }) } }) }})有没有看到这些以"})“结尾的金字塔结构?由于回调函数是异步的,在上面的代码中每一层的回调函数都需要依赖上一层的回调执行完,所以形成了层层嵌套的关系最终形成类似上面的回调地狱。2. 代码层面解决回调地狱1. 保持代码简短var form = document.querySelector(‘form’)form.onsubmit = function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value request({ uri: “http://example.com/upload", body: name, method: “POST” }, function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’) if (err) return statusMessage.value = err statusMessage.value = body })}可以看到,上面的代码给两个函数加了描述性功能名称,使代码更容易阅读,当发生异常时,你将获得引用实际函数名称而不是“匿名”的堆栈跟踪。现在我们可以将这些功能移到我们程序的顶层:document.querySelector(‘form’).onsubmit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse);} function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}重新整改代码结构之后,可以清晰的看到这段函数的功能。2. 模块化从上面取出样板代码,并将其分成几个文件,将其转换为模块。这是一个名为formuploader.js的新文件,它包含了之前的两个函数:module.exports.submit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse)}function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}把它们exports后,在应用程序中引入并使用,这就使得代码更加简洁易懂了:var formUploader = require(‘formuploader’);document.querySelector(‘form’).onsubmit = formUploader.submit;3. error first处理每一处错误,并且回调的第一个参数始终保留用于错误:var fs = require(‘fs’) fs.readFile(’/Does/not/exist’, handleFile); function handleFile (error, file) { if (error) return console.error(‘Uhoh, there was an error’, error); // otherwise, continue on and use file in your code; }有第一个参数是错误是一个简单的惯例,鼓励你记住处理你的错误。如果它是第二个参数,会更容易忽略错误。除了上述代码层面的解决方法,还可以使用以下更高级的方法,也是另外4种实现异步的方法。但是请记住,回调是JavaScript的基本组成部分(因为它们只是函数),在学习更先进的语言特性之前学习如何读写它们,因为它们都依赖于对回调。2. 发布订阅模式订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。比如有个界面是实时显示天气,它就订阅天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。简单来说,发布订阅模式,有一个事件池,用来给你订阅(注册)事件,当你订阅的事件发生时就会通知你,然后你就可以去处理此事件。使用发布订阅模式,来修改Ajax:xhr.onreadystatechange = function () {//监听事件 if (this.readyState === 4) { if (this.status === 200) { switch (dataType) { case ‘json’: { Event.emit(‘data ‘+method,JSON.parse(this.responseText)); //触发事件 break; } case ’text’: { Event.emit(‘data ‘+method,this.responseText); break; } case ‘xml’: { Event.emit(‘data ‘+method,this.responseXML); break; } default: { break; } } } }}3. PromiseES6将Promise写进了语言标准,统一了用法,原生提供了Promise对象。Promise,简单说就是一个容器,里面保存着一个异步操作的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise有3种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。Promise很重要的两个特点:状态不受外界影响;只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。1. 基本用法const p = new Promise((resolve,reject) => { // resolve在异步操作成功时调用 resolve(‘success’); // reject在异步操作失败时调用 reject(’error’);});p.then(result => { console.log(result);});p.catch(result => { console.log(result);})ES6规定,Promise对象是一个构造函数,用来生成Promise实例。new一个Promise实例时,这个对象的起始状态就是Pending状态,再根据resolve或reject返回Fulfilled状态 / Rejected状态。2. Promise.prototype.then( )前面可以看到,Promise实例具有then方法,所以then方法是定义在原型对象Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例,因此then可以采用链式写法:getJSON("/posts.json”).then(function(json) { return json.post;}).then(function(post) { // …});3. Promise.prototype.catch( )Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。getJSON(’/posts.json’).then(function(posts) { // …}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log(‘发生错误!’, error);});4. Promise.all( )Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。const p = Promise.all([p1, p2, p3]);上面代码中,p的状态由p1、p2、p3决定,分成两种情况:只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。5. Promise.race( )Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。不同的是,race()接受的对象中,哪个对象返回快就返回哪个对象,如果指定时间内没有获得结果,就将Promise的状态变为reject。const p = Promise.race([ fetch(’/resource-that-may-take-a-while’), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(‘request timeout’)), 5000) })]);p.then(console.log).catch(console.error);上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。6. Promise.resolve( )Promise.resolve(‘foo’)// 等价于new Promise(resolve => resolve(‘foo’))7. Promise.reject( )const p = Promise.reject(‘出错了’);// 等同于const p = new Promise((resolve, reject) => reject(‘出错了’))p.then(null, function (s) { console.log(s)});// 出错了下面是一个用Promise对象实现的Ajax操作的例子:const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open(“GET”, url); client.onreadystatechange = handler; client.responseType = “json”; client.setRequestHeader(“Accept”, “application/json”); client.send(); }); return promise;};getJSON("/posts.json”).then(function(json) { console.log(‘Contents: ’ + json);}, function(error) { console.error(‘出错了’, error);});8. callbackify & promisifyNode 8提供了两个工具函数util.promisify、util.callbackify用于在回调函数和Promise之间做方便的切换,我们也可以用JavaScript代码来实现一下。1. promisify:把callback转化为promisefunction promisify(fn_callback) { //接收一个有回调函数的函数,回调函数一般在最后一个参数 if(typeof fn_callback !== ‘function’) throw new Error(‘The argument must be of type Function.’); //返回一个函数 return function (…args) { //返回Promise对象 return new Promise((resolve, reject) => { try { if(args.length > fn_callback.length) reject(new Error(‘arguments too much.’)); fn_callback.call(this,…args,function (…args) { //nodejs的回调,第一个参数为err, Error对象 args[0] && args[0] instanceof Error && reject(args[0]); //除去undefined,null参数 args = args.filter(v => v !== undefined && v !== null); resolve(args); }.bind(this)); //保证this还是原来的this } catch (e) { reject(e) } }) }}2. callbackify:promise转换为callbackfunction callbackify(fn_promise) { if(typeof fn_promise !== ‘function’) throw new Error(‘The argument must be of type Function.’); return function (…args) { //返回一个函数 最后一个参数是回调 let callback = args.pop(); if(typeof callback !== ‘function’) throw new Error(‘The last argument must be of type Function.’); if(fn_promise() instanceof Promise){ fn_promise(args).then(data => { //回调执行 callback(null,data) }).catch(err => { //回调执行 callback(err,null) }) }else{ throw new Error(‘function must be return a Promise object’); } }}个人而言,最好直接把代码改成promise形式的,而不是对已有的callback加上这个中间层,因为其实改动的成本差不多。但总有各种各样的情况,比如,你的回调函数已经有很多地方使用了,牵一发而动全身,这时这个中间层还是比较有用的。4. generator(生成器)函数Generator函数是ES6提供的一种异步编程解决方案,通过yield标识位和next()方法调用,实现函数的分段执行。1. next( )方法先从下面的例子看一下Generator函数是怎么定义和运行的。function gen() { yield “hello”; yield “generator”; return;}gen(); // 没有输出结果var g = gen();console.log(g.next()); // { value: ‘hello’, done: false }console.log(g.next()); // { value: ‘generator’, done: false }console.log(g.next()); // { value: ‘undefined’, done: true }从上面可以看到,Generator函数定义时要带,在直接执行gen()时,没有像普通的函数一样,输出结果,而是通过调用next()方法得到了结果。这个例子中我们引入了yield关键字,分析下这个执行过程:创建了g对象,指向gen的句柄第一次调用next(),执行到yield hello,暂缓执行,并返回了hello第二次调用next(),继续上一次的执行,执行到yield generator,暂缓执行,并返回了generator第三次调用next(),直接执行return,并返回done:true,表明结束。经过上面的分析,yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置。next()方法返回的结果是个对象,对象里面的value是运行结果,done表示是否运行完成。2. throw( )方法throw()方法在函数体外抛出一个错误,然后在函数体内捕获。function *gen1() { try{ yield; } catch(e) { console.log(‘内部捕获’) }}let g1 = gen1();g1.next();g1.throw(new Error());3. return( )方法return()方法返回给定值,并终结生成器,在return后面的yield不会再被执行。function *gen2(){ yield 1; yield 2; yield 3;}let g2 = gen2();g2.next(); // { value:1, done:false }g2.return(); // { value:undefined, done:true }g2.next(); // { value:undefined, done:true }5. Promise + async & await在ES2017中,提供了async / await两个关键字来实现异步,是异步编程的最高境界,就是根本不用关心它是否是异步,很多人认为它是异步编程的终极解决方案。async / await寄生于Promise,本质上还是基于Generator函数,可以说是Generator函数的语法糖,async用于申明一个function是异步的,而await可以认为是async wait的简写,等待一个异步方法执行完成。async function demo() { let result = await Promise.resolve(123); console.log(result);}demo();async函数返回的是一个Promise对象,在上述例子中,表示demo是一个async函数,await只能用在async函数里面,表示等待Promise返回结果后,再继续执行,await后面应该跟着Promise对象(当然,跟着其他返回值也没关系,只是会立即执行,这样就没有意义了)。Promise虽然一方面解决了callback的回调地狱,但是相对的把回调 “纵向发展” 了,形成了一个回调链:function sleep(wait) { return new Promise((res,rej) => { setTimeout(() => { res(wait); },wait); });}/let p1 = sleep(100);let p2 = sleep(200);let p =/sleep(100).then(result => { return sleep(result + 100);}).then(result02 => { return sleep(result02 + 100);}).then(result03 => { console.log(result03);})将上述代码改成async/await写法:async function demo() { let result01 = await sleep(100); //上一个await执行之后才会执行下一句 let result02 = await sleep(result01 + 100); let result03 = await sleep(result02 + 100); // console.log(result03); return result03;}demo().then(result => { console.log(result);});因为async返回的也是promise对象,所以用then接收就行了。如果是reject状态,可以用try-catch捕捉:let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { try { let result = await p; } catch(e) { console.log(e); }}demo();这是基本的错误处理,但是当内部出现一些错误时,和Promise有点类似,demo()函数不会报错,还是需要catch回调捕捉,这就是内部的错误被 “静默” 处理了。let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { // try { let result = name; // } catch(e) { // console.log(e); // }}demo().catch((err) => { console.log(err);})最后,总结一下JavaScript实现异步的5种方式的优缺点:回调函数:写起来方便,但是过多的回调会产生回调地狱,代码横向扩展,不易于维护和理解。发布订阅模式:方便管理和修改事件,不同的事件对应不同的回调,但是容易产生一些命名冲突的问题,事件到处触发,可能代码可读性不好。Promise对象:通过then方法来替代掉回调,解决了回调产生的参数不容易确定的问题,但是相对的把回调 “纵向发展” 了,形成了一个回调链。Generator函数:确实很好的解决了JavaScript中异步的问题,但是得依赖执行器函数。async/await:这可能是javascript中,解决异步的最好的方式了,让异步代码写起来跟同步代码一样,可读性和维护性都上来了。 ...

April 9, 2019 · 4 min · jiezi

顺序加载图片方法

普通图片加载//普通图片加载var imglist = [‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,‘https://github.githubassets.com/images/search-key-slash.svg’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’];loadImg(imglist);function loadImg(imglist){ var imgarr = []; var curimg = 0; var body = document.getElementsByTagName(‘body’); for(var i =0;i<imglist.length;i++){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; img.onload = function(){ console.log(‘show ‘+this.id) } imgarr.push(img); body[0].appendChild(img); }}顺序加载图片方法1//顺序加载图片方法1、function loadImg(imglist){ var imgarr = []; var curimg = 0; var body = document.getElementsByTagName(‘body’); for(var i =0;i<imglist.length;i++){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.onload = function(){ curimg +=1; if(curimg < imgarr.length){ body[0].appendChild(imgarr[curimg]); console.log(‘show ‘+curimg) } } imgarr.push(img); } body[0].appendChild(imgarr[0]);}promise方法//promise方法var imglist = [‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,‘https://github.githubassets.com/images/search-key-slash.svg’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’];var body = document.getElementsByTagName(‘body’);function loadimg(i){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; body[0].appendChild(img); return new Promise(function(resolve,reject){ img.onload = function(){ console.log(‘show ‘+this.id) resolve(i+1) }; })}var a = Promise.resolve(0);var i=0;while(i<imglist.length-1){ a = a.then(function(i){ console.log(’load ‘+i) return loadimg(i) }); i++;}async 方法//async 方法async function loadImages(imglist){ for(var i =0; i<imglist.length; i++){ console.log(’load ‘+i) await loadImg(i); //执行完成之后才走下一步; console.log(‘finish ‘+i); }}async function loadImg(i){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; body[0].appendChild(img); return new Promise(function(resolve,reject){ img.onload = function(){ console.log(‘show ‘+this.id) resolve(i+1); } })} ...

March 1, 2019 · 1 min · jiezi

Ajax Fetch 和 Axios(持续更新中...)

Ajax Fetch 和 Axios(持续更新中…)知识点梳理AJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。JavaScript 代码都是单线程执行的,由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,回调函数不好看,不利于代码复用,而链式写法好处 逻辑统一、利于复用,所以出现 Primose Promise 有各种开源实现,在 ES6 中被统一规范,由浏览器直接支持。async await 是 Promise 语法糖,使异步的逻辑书写标准的同步函数Generator原生 XHRAjaxAJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。属性描述onreadystatechange每当 readyState 属性改变时,就会调用该函数。readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0: 请求未初始化 1: 服务器连接已建立,open()方法已调用,但是 send()方法未调用 2: 请求已连接 send()方法已调用,HTTP 请求已发送到 Web 服务器。未接收到相应3: 请求处理中 4: 请求已完成,且响应已就绪status200: “OK"404: 未找到页面;(function() { var xmlhttp if (window.XMLHttpRequest) { // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp = new XMLHttpRequest() } else { // IE6, IE5 浏览器执行代码 xmlhttp = new ActiveXObject(‘Microsoft.XMLHTTP’) } xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { console.log(xmlhttp.responseText) } } xmlhttp.open(‘GET’, ‘http://www.runoob.com/try/ajax/ajax_info.txt', true) xmlhttp.send()})()Ajax 安全限制浏览器的同源策略导致的。默认情况下,JavaScript 在发送 AJAX 请求时,URL 的域名必须和当前页面完全一致跨域请求通过 Flash 插件发送 HTTP 请求,这种方式可以绕过浏览器的安全限制,但必须安装 Flash,并且跟 Flash 交互。不过 Flash 用起来麻烦,而且现在用得也越来越少了。通过在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器JSONP 它有个限制,只能用 GET 请求,并且要求返回 JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用 JavaScript 资源CORS CORS 全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。面这种跨域请求,称之为“简单请求”。简单请求包括 GET、HEAD 和 POST(POST 的 Content-Type 类型仅限 application/x-www-form-urlencoded、multipart/form-data 和 text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足 90%的需求Promise在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现function callback() { console.log(‘Done’)}console.log(‘before setTimeout()’)setTimeout(callback, 1000) // 1秒钟后调用callback函数console.log(‘after setTimeout()’)链式写法的好处在于,先统一执行 AJAX 逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用 success 函数或 fail 函数。古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在 JavaScript 中称为 Promise 对象。使用 Promise 封装 ajax 简化异步处理// ajax函数将返回Promise对象:function ajax(method, url, data) { var request = new XMLHttpRequest() return new Promise(function(resolve, reject) { request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText) } else { reject(request.status) } } } request.open(method, url) request.send(data) })}var p = ajax(‘GET’, ‘/api/categories’)p.then(function(text) { // 如果AJAX成功,获得响应内容}).catch(function(status) { // 如果AJAX失败,获得响应代码})Promise 使用方法;(function() { console.time(‘doIt’) const time1 = 300 step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(result is ${result}) console.timeEnd(‘doIt’) })})()function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n) })}function step1(n) { console.log(step1 with ${n}) return takeLongTime(n)}function step2(n) { console.log(step2 with ${n}) return takeLongTime(n)}function step3(n) { console.log(step3 with ${n}) return takeLongTime(n)}Promise.all()两个任务是可以并行执行的,用 Promise.all()实现var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,并在它们都完成后执行then:Promise.all([p1, p2]).then(function(results) { console.log(results) // 获得一个Array: [‘P1’, ‘P2’]})Promise.race()有些时候,多个异步任务是为了容错,只需要获得先返回的结果即可var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 3000, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,其中一个完成后执行then:Promise.race([p1, p2]).then(function(results) { console.log(results) // // ‘P1’})async awaitawaitawait 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。// 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x) }, 2000) })}async function f1() { var x = await resolveAfter2Seconds(10) console.log(x) // 10}f1()// 如果 Promise 处理异常,则异常值被抛出。async function f3() { try { var z = await Promise.reject(30) } catch (e) { console.log(e) // 30 }}f3()asyncasync function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。一个返回的 Promise 对象会以 async function 的返回值进行解析(resolved),或者以该函数抛出的异常进行回绝(rejected)。async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复 async 函数的执行并返回解析值(resolved)。function resolveAfter2Seconds() { return new Promise(resolve => { setTimeout(() => { resolve(‘resolved’) }, 2000) })}async function asyncCall() { console.log(‘calling’) var result = await resolveAfter2Seconds() console.log(result) // expected output: ‘resolved’}asyncCall()GeneratorFetchFetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。Fetch 是挂在在 window 下的Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。Fetch 还利用到了请求的异步特性——它是基于 Promise 的。fetch(‘http://example.com/movies.json') .then(function(response) { return response.json() }) .then(function(myJson) { console.log(myJson) })postData(‘http://example.com/answer', { answer: 42 }) .then(data => console.log(data)) // JSON from response.json() call .catch(error => console.error(error))function postData(url, data) { // Default options are marked with * return fetch(url, { body: JSON.stringify(data), // must match ‘Content-Type’ header cache: ’no-cache’, // *default, no-cache, reload, force-cache, only-if-cached credentials: ‘same-origin’, // include, same-origin, *omit headers: { ‘user-agent’: ‘Mozilla/4.0 MDN Example’, ‘content-type’: ‘application/json’ }, method: ‘POST’, // *GET, POST, PUT, DELETE, etc. mode: ‘cors’, // no-cors, cors, *same-origin redirect: ‘follow’, // manual, *follow, error referrer: ’no-referrer’ // *client, no-referrer }).then(response => response.json()) // parses response to JSON}参考AJAXPromiseasync await 语法描述Fetch 语法描述 ...

February 21, 2019 · 3 min · jiezi

JavaScript ES6 async/await的简单学习demo

传统回调函数// demo1-callback.js/** 现在我们要做个事情,写个回调函数,每秒输出一个递增的数字,输出三次 普通回调函数的写法 /function logNumber(n, callback){ setTimeout(() => { console.log(n); n++; callback(n) }, 1000);}// 现在调用它logNumber(1, function(n){ logNumber(n, function(m){ logNumber(m, function(q){ }) })})Promise// demo2-promise.js/* 现在我们改用promise来重写demo1的函数 /// 我们在这里暴露那个promise以供demo3调用function generatorLogNumber(n){ return new Promise(res => { setTimeout(() => { console.log(n); n++; res(n) }, 1000); })}// 现在使用它generatorLogNumber(1) .then(n => { generatorLogNumber(n) .then(m => { generatorLogNumber(m) .then(q => { }) }) })// 这里把这个promise暴露出去以供demo3使用,记得把本demo的调用函数注释掉(就是15-24行注释掉)module.exports = generatorLogNumber;async/await// demo3-async-await.js/* 现在我们改用更加方便的async/await方式来调用demo2的promise */// 首先把那个promise引入进来const generatorLogNumber = require(’./demo2-promise.js’);(async () => {//双括号表示立即执行的匿名函数 const n = await generatorLogNumber(1); const m = await generatorLogNumber(n); const q = await generatorLogNumber(m);})()// 可以node demo3-async-await.js 来运行看看 ...

January 29, 2019 · 1 min · jiezi

asnyc/await的并行

一直以为es7里面的async和await可以用来简化串行异步代码,而没有想到还能并行。说到底,这俩货不过是promise的语法糖,await的作用只是串行解析promise。通常我们这样写:function asyncAwaitFn(str) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(str) }, 1000); })}const parallel = async () => { //并行执行 console.time(‘parallel’) const parallelOne = await asyncAwaitFn(‘1’); const parallelTwo = await asyncAwaitFn(‘2’) console.log(parallelOne) //1 console.log(parallelTwo) //2 console.timeEnd(‘parallel’) //2003.509033203125ms}parallel()这是串行,显然最后的执行时间应该大于2000ms。但如果换一种写法:function asyncAwaitFn(str) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(str) }, 1000); })}const parallel = async () => { //并行执行 console.time(‘parallel’) const parallelOne = asyncAwaitFn(‘1’); const parallelTwo = asyncAwaitFn(‘2’) console.log(await parallelOne) //1 console.log(await parallelTwo) //2 console.timeEnd(‘parallel’) //1001.87255859375ms}parallel()最后执行时间只要1000ms,显然是并行了。不过严谨来说,这依然是promise本身的并行罢了。 ...

January 24, 2019 · 1 min · jiezi

promise, async, await, execution order

async can be transformed to promise. So, if we want to understand async, we have to understand promise first.PromiseNormally, promise is easy to understand, especially when using like this:promise .then(() => { // }) .then(() => { // }) .then(() => { // })then after then would make the order of async callbacks clear. Actually we shouldn’t rely on async callbacks order. But a certain execution order would make me feel more comfortable. However, sometimes, things are different.RESOLVE and Promise.resolve()Normally, I initialize a promise by Promise.resolve() because it seems too troublesome to use Promise constructor like below which I called it RESOLVE in this article.new Promise((resolve,reject)=>{ resolve()})And normally, I used it by Promise.resolve(non-thenable) which is equivalent to RESOLVE(non-thenable)new Promise((resolve, reject) => { resolve(non-thenable)})So, it doesn’t matter which one you choose. RESOLVE(non-thenable) or Promise.resolve(non-thenable). However, when it comes to thenable, things are different. Promise.resolve(thenable) is not equivalent to RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})I explained it carefully in What’s the difference between resolve(promise) and resolve(’non-thenable-object’)?. And here is the conclusion:for non-thenable, Promise.resolve(non-thenable) is equivalent to RESOLVE(non-thenable)for thenable, Promise.resolve(thenable) is not equivalent to RESOLVE(thenable) because RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})is equivalent tonew Promise((resolve, reject) => { Promise.resolve().then(() => { thenable.then(resolve) })})according to spec. It’s obviously not equivalent to Promise.resolve(thenable). You can test it by this example:let p1 = Promise.resolve(1)Promise.resolve(p1).then(res => { console.log(res)})p1.then(res => { console.log(2)})//1//2whilelet p1 = Promise.resolve(1)new Promise((resolve, reject) => { resolve(p1)}).then(res => { console.log(res)})p1.then(res => { console.log(2)})//2//1So, here comes another question. When would we use Promise.resolve(thenable) or RESOLVE(thenable)? It doesn’t seem to be that common.Yes, indeed. Except async and await.async and awaitAs we all know or spec says that the result of async returns promise. For example:(async function(){}()).toString() //"[object Promise]“And await can be used in async.awaitSo, how does await work in async? According to spec:Await:We can transform await codeconst p1 = Promise.resolve(1)const async1 = async function() { const res1 = await p1 console.log(res1)}async1()p1.then(() => console.log(‘after gen’))toconst p1 = Promise.resolve(1)const async1 = async function() { new Promise(resolve => { resolve(p1) }).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))The result is the same:after gen1on chrome 70. However, in chrome canary 73 the former result is1after genWhy? The reason can be found in https://github.com/tc39/ecma2… Simply say, the spec to await was going to change to:What’s difference? The difference is exactly the difference between RESOLVE(thenable) and Promise.resolve(thenable).In the past and chrome 70, we are using RESOLVE(thenable), so I transformed the code like above. If using this new spec, the code should be transformed toconst p1 = Promise.resolve(1)const async1 = async function() { Promise.resolve(p1).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))In this case, chrome 70 and canary 73 would all get1after genThat’s the spec change for await. Hope you both understand the way before and after change.asyncNow, let’s talk about async. How does async work? According to spec:The spawn used in the above desugaring is a call to the following algorithm. This algorithm does not need to be exposed directly as an API to user code, it is part of the semantics of async functions.And the spawn isfunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}However, I think the spawn is the future version which doesn’t apply to chrome 70 because it used Promise.resolve(next.value) instead of RESOLVE(next.value) to transform await. So, I thought the old version or version applied to chrome 70 should befunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again /* modified line / new Promise(resolve => resolve(next.value)).then( / origin line / // Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}You can tested it by comparing the result of below example.const p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const async1 = async function () { const res1 = await p1 console.log(res1) const res2 = await p2 console.log(res2)}async1()p1.then(() => console.log(‘after gen’))withconst p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const gen = function () { const res1 = yield p1 console.log(res1) const res2 = yield p2 console.log(res2)}const async1Eq = function () { spawn(gen, this)}async1Eq()p1.then(() => console.log(‘after gen’))The result would be:On chrome 70, with the former spawn, you will get the different result. While you will get the same result with the latter spawn.In the same way, on chrome 73, with the former spawn, you will get the same result. While you will get the different result with the latter spawn.Origin Post ...

December 27, 2018 · 5 min · jiezi

「今日头条」前端面试题和思路解析

一篇文章和一道面试题最近,有篇名为 《8张图帮你一步步看清 async/await 和 promise 的执行顺序》 的文章引起了我的关注。作者用一道2017年「今日头条」的前端面试题为引子,分步讲解了最终结果的执行原因。其中涉及到了不少概念,比如异步的执行顺序,宏任务,微任务等等,同时作者限定了执行范围,以浏览器的 event loop 机制为准。下面是原题的代码:async function async1 () { console.log(‘async1 start’); await async2(); console.log(‘async1 end’);}async function async2 () { console.log(‘async2’);}console.log(‘script start’);setTimeout(function () { console.log(‘setTimeout’);}, 0);async1();new Promise(function (resolve) { console.log(‘promise1’); resolve();}).then(function () { console.log(‘promise2’);});console.log(‘script end’);紧接着,作者先给出了答案。并希望读者先行自我测试。script startasync1 startasync2promise1script endpromise2async1 endsetTimeout我在看这道题的时候,先按照自己的理解写出了结果。script startasync1 startasync2promise1script endasync1 endpromise2setTimeout一些重要的概念这里需要先简单地说一些 event loop 的概念。Javascript是单线程的,所有的同步任务都会在主线程中执行。主线程之外,还有一个任务队列。每当一个异步任务有结果了,就往任务队列里塞一个事件。当主线程中的任务,都执行完之后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行。异步任务之间,会存在差异,所以它们执行的优先级也会有区别。大致分为 微任务(micro task,如:Promise、MutaionObserver等)和宏任务(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循环中,微任务永远在宏任务之前执行。主线程会不断重复上面的步骤,直到执行完所有任务。另外,还有 async/await 的概念。async 函数,可以理解为是Generator 函数的语法糖。它建立在promise之上,总是与await一起使用的。await会返回一个Promise 对象,或者一个表达式的值。其目的是为了让异步操作更优雅,能像同步一样地书写。我的理解再说说我对这道题的理解。首先,从console的数量上看,会输出8行结果。再瞟了一眼代码,看到了setTimeout,于是,默默地把它填入第8行。在setTimeout附近,看到了 console.log( ‘script start’ ) 和 async1(),可以确认它们是同步任务,会先在主线程中执行。所以,妥妥地在第1行填入 script start,第2行填入async1方法中的第一行 async1 start。接下来,遇到了await。从字面意思理解,让我们等等。需要等待async2()函数的返回,同时会阻塞后面的代码。所以,第3行填入 async2。讲道理,await都执行完了,该轮到console.log( ‘async1 end’ )的输出了。但是,别忘了下面还有个Promise,有一点需要注意的是:当 new 一个 Promise的时候,其 resolve 方法中的代码会立即执行。如果不是 async1()的 await 横插一杠,promise1 可以排得更前面。所以,现在第4行填入 promise1。再接下来,同步任务 console.log( ‘script end’ ) 执行。第5行填入 script end。还有第6和第7行,未填。回顾一下上面提到 async/await 的概念,其目的是为了让异步能像同步一样地书写。那么,我认为 console.log( ‘async1 end’ ) 就是个同步任务。所以,第6行填入async1 end。最后,顺理成章地在第7行填入 promise2。与作者答案的不同回过头对比与作者的答案,发现第6和第7行的顺序有问题。再耐心地往下看文章,反复地看了几遍 async1 end 和 promise2 谁先谁后,还是无法理解为何在chrome浏览器中,promise2 会先于 async1 end 输出。然后,看到评论区,发现也有人提出了相同的疑惑。@rhinel提出,在他的72.0.3622.0(正式版本)dev(64 位)的chrome中,跑出来的结果是 async1 end 在 promise2 之前。随即我想到了一种可能,JS的规范可能会在未来有变化。于是,我用自己的react工程试了一下(工程中的babel-loader版本为7.1.5。.babelrc的presets设置了stage-3),结果与我的理解一致。当前的最新版本 chromeV71,在这里的执行顺序上,的确存在有问题。于是,我也在评论区给作者留了言,进行了讨论。@rhinel最后也证实,其实最近才发布通过了这个顺序的改进方案,这篇 《Faster async functions and promises》 详细解释了这个改进,以及实现效果。不久之后,作者也在他文章的最后,补充了我们讨论的结果,供读者参考。总结最后,我想说的是,本文虽然只是由一道面试题引申出的,对浏览器执行顺序的思考、讨论与验证的过程。但正是因为有了这些过程,才让更多的思想得以碰撞,概念进一步得以理解,规范得以明了。有机会的话,希望能有与更多的同道,多多交流。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

December 22, 2018 · 1 min · jiezi

细说async/await相较于Promise的优势

前言介于上一篇 「今日头条」前端面试题和思路解析 中提到的 async/await,让我想起了之前写过的一篇文章,在此做个分享。它细说了什么是async函数,以及其相较于 Promise 的优势。温故而知新,正文开始。async 函数是什么?谈及异步回调函数的嵌套,总会让人感到烦躁,特别是当业务逻辑复杂,往往需要调用几次 ajax 才能拿到所有需要的数据。从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。所以,我们需要一种方法,更优雅地解决异步操作。于是,async函数出现了。一句话解释:async 函数,就是 Generator 函数的语法糖。它有以下几个特点:建立在promise之上。所以,不能把它和回调函数搭配使用。但它会声明一个异步函数,并隐式地返回一个Promise。因此可以直接return变量,无需使用Promise.resolve进行转换。和promise一样,是非阻塞的。但不用写 then 及其回调函数,这减少代码行数,也避免了代码嵌套。而且,所有异步调用,可以写在同一个代码块中,无需定义多余的中间变量。它的最大价值在于,可以使异步代码,在形式上,更接近于同步代码。它总是与await一起使用的。并且,await 只能在 async 函数体内。await 是个运算符,用于组成表达式,它会阻塞后面的代码。如果等到的是 Promise 对象,则得到其 resolve值。否则,会得到一个表达式的运算结果。为何说 async 函数是语法糖async 函数的实现,其实就是将 Generator 函数和自动执行器,包装在一个函数里。下面的这个例子,来自阮老师的 《async 函数的含义和用法》 一文。async function fn(args) { // …}// 等同于function fn(args) { return spawn(function*() { // … });}// spawn 函数就是自动执行器function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); });}所以说,async 函数是 Generator 函数的语法糖。另外,它相对较新,属于ES7中的语法。但是转码器 Babel 已经支持,转码后就能使用。async 相较于 Promise 的优势1.相比于 Promise,它能更好地处理then链function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); });}function step1(n) { console.log(step1 with ${n}); return takeLongTime(n);}function step2(n) { console.log(step2 with ${n}); return takeLongTime(n);}function step3(n) { console.log(step3 with ${n}); return takeLongTime(n);}现在用 Promise 方式来实现这三个步骤的处理。function doIt() { console.time(“doIt”); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(result is ${result}); });}doIt();// step1 with 300// step2 with 500// step3 with 700// result is 900如果用 async/await 来实现的话,会是这样:async function doIt() { console.time(“doIt”); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(result is ${result});}doIt();结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。2.中间值现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。Pomise的实现看着很晕,传递参数太过麻烦。function doIt() { console.time(“doIt”); const time1 = 300; step1(time1) .then(time2 => { return step2(time1, time2) .then(time3 => [time1, time2, time3]); }) .then(times => { const [time1, time2, time3] = times; return step3(time1, time2, time3); }) .then(result => { console.log(result is ${result}); });}doIt();用 async/await 来写:async function doIt() { console.time(“doIt”); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time1, time2); const result = await step3(time1, time2, time3); console.log(result is ${result});}doIt();没有多余的中间值,更加优雅地实现了。3.调试相比于 Promise 更易于调试。因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,因为调试器只能跟踪同步代码的每一步。现在,如果使用 async/await,你就不必再使用箭头函数。你可以对 await 语句执行步进操作,就好像他们都是普通的同步语句一样。总结JavaScript的异步编写方式,从 回调函数 到 Promise、Generator 再到 Async/Await。表面上只是写法的变化,但本质上则是语言层的一次次抽象。让我们可以用更简单的方式实现同样的功能,而不需要去考虑代码是如何执行的。换句话说就是:异步编程的最高境界,就是根本不用关心它是不是异步。参考文献async 函数的含义和用法(译) 6个Async/Await优于Promise的方面PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

December 22, 2018 · 2 min · jiezi